Prettier reformat

This commit is contained in:
2023-05-20 21:29:27 -05:00
parent d9285647c2
commit 6e27447a99
18 changed files with 628 additions and 493 deletions

View File

@@ -12,6 +12,6 @@ const config = {
i18n: {
locales: ["en"],
defaultLocale: "en",
}
},
};
export default config;

View File

@@ -11,7 +11,7 @@
* @returns {number} The relative position of the value to the range as -1, 0 or 1.
*/
function compareASN(value: number, range: string): number {
const [start, end] = range.split('-', 2) as [string, string];
const [start, end] = range.split("-", 2) as [string, string];
if (value < parseInt(start)) return -1;
if (value > parseInt(end)) return 1;
return 0;

View File

@@ -5,14 +5,17 @@ import {format, formatDistanceToNow} from "date-fns";
type DynamicDateProps = {
value: Date | number;
absoluteFormat?: string;
}
};
/**
* A component for a toggleable switch between the absolute & human-relative date.
* @param value The date to be displayed, the Date value, or
* @param absoluteFormat Optional - the date-fns format string to use for the absolute date rendering.
*/
const DynamicDate: FunctionComponent<DynamicDateProps> = ({value, absoluteFormat}) => {
const DynamicDate: FunctionComponent<DynamicDateProps> = ({
value,
absoluteFormat,
}) => {
const { value: showAbsolute, toggle: toggleFormat } = useBoolean(true);
const date = new Date(value);
@@ -21,10 +24,13 @@ const DynamicDate: FunctionComponent<DynamicDateProps> = ({value, absoluteFormat
<span title={date.toISOString()}>
{showAbsolute
? format(date, absoluteFormat ?? "LLL do, y HH:mm:ss")
: formatDistanceToNow(date, {includeSeconds: true, addSuffix: true})}
: formatDistanceToNow(date, {
includeSeconds: true,
addSuffix: true,
})}
</span>
</button>
)
}
);
};
export default DynamicDate;

View File

@@ -9,11 +9,18 @@ type PropertyProps = {
valueClass?: string;
};
const Property: FunctionComponent<PropertyProps> = ({title, children, titleClass, valueClass}) => {
return <>
const Property: FunctionComponent<PropertyProps> = ({
title,
children,
titleClass,
valueClass,
}) => {
return (
<>
<dt className={titleClass}>{title}:</dt>
<dd className={classNames("mt-2 mb-2 ml-6", valueClass)}>{children}</dd>
</>
}
);
};
export default Property;

View File

@@ -2,7 +2,7 @@ import type {FunctionComponent} from "react";
import React from "react";
import { rdapStatusInfo } from "@/constants";
import type { Domain } from "@/types";
import Events from "@/components/lookup/Events"
import Events from "@/components/lookup/Events";
import Property from "@/components/common/Property";
export type DomainProps = {
@@ -10,33 +10,36 @@ export type DomainProps = {
};
const DomainCard: FunctionComponent<DomainProps> = ({ data }: DomainProps) => {
return <div className="card">
<div className="card-header">{data.ldhName ?? data.unicodeName} ({data.handle})</div>
return (
<div className="card">
<div className="card-header">
{data.ldhName ?? data.unicodeName} ({data.handle})
</div>
<div className="card-body">
<dl>
{data.unicodeName != undefined ? <Property title="Unicode Name">
{data.unicodeName}
</Property> : null}
{data.unicodeName != undefined ? (
<Property title="Unicode Name">{data.unicodeName}</Property>
) : null}
<Property title={data.unicodeName != undefined ? "LHD Name" : "Name"}>
{data.ldhName}
</Property>
<Property title="Handle">
{data.handle}
</Property>
<Property title="Handle">{data.handle}</Property>
<Property title="Events">
<Events key={0} data={data.events} />
</Property>
<Property title="Status">
<ul key={2} className="list-disc">
{data.status.map((statusKey, index) =>
{data.status.map((statusKey, index) => (
<li key={index}>
<span title={rdapStatusInfo[statusKey]}>{statusKey}</span>
</li>)}
</li>
))}
</ul>
</Property>
</dl>
</div>
</div>
}
);
};
export default DomainCard;

View File

@@ -5,21 +5,23 @@ import DynamicDate from "@/components/common/DynamicDate";
export type EventsProps = {
data: Event[];
}
};
const Events: FunctionComponent<EventsProps> = ({ data }) => {
return <dl>
return (
<dl>
{data.map(({ eventAction, eventDate, eventActor }, index) => {
return <Fragment key={index}>
<dt className="font-weight-bolder">
{eventAction}:
</dt>
return (
<Fragment key={index}>
<dt className="font-weight-bolder">{eventAction}:</dt>
<dd>
<DynamicDate value={new Date(eventDate)} />
{eventActor != null ? ` (by ${eventActor})` : null}
</dd>
</Fragment>
);
})}
</dl>
}
);
};
export default Events;

View File

@@ -1,8 +1,19 @@
import type { FunctionComponent } from "react";
import DomainCard from "@/components/lookup/DomainCard";
import type {Domain, AutonomousNumber, Entity, Nameserver, IpNetwork} from "@/types";
import type {
Domain,
AutonomousNumber,
Entity,
Nameserver,
IpNetwork,
} from "@/types";
export type ParsedGeneric = Domain | Nameserver | Entity | AutonomousNumber | IpNetwork;
export type ParsedGeneric =
| Domain
| Nameserver
| Entity
| AutonomousNumber
| IpNetwork;
export type ObjectProps = {
data: ParsedGeneric;
};
@@ -10,18 +21,19 @@ export type ObjectProps = {
const Generic: FunctionComponent<ObjectProps> = ({ data }: ObjectProps) => {
switch (data.objectClassName) {
case "domain":
return <DomainCard data={data}/>
return <DomainCard data={data} />;
case "autnum":
case "entity":
case "ip network":
case "nameserver":
default:
return <div className="card my-2">
<div className="card-header">Not implemented. (
<pre>{data.objectClassName ?? "null"}</pre>
)
return (
<div className="card my-2">
<div className="card-header">
Not implemented. (<pre>{data.objectClassName ?? "null"}</pre>)
</div>
</div>
);
}
// const title: string = (data.unicodeName ?? data.ldhName ?? data.handle)?.toUpperCase() ?? "Response";
@@ -29,6 +41,6 @@ const Generic: FunctionComponent<ObjectProps> = ({data}: ObjectProps) => {
// <div className="card-header">{title}</div>
// {objectFragment}
// </div>
}
};
export default Generic;

View File

@@ -2,59 +2,91 @@
import type { RdapStatusType, RootRegistryType, TargetType } from "@/types";
export const rdapStatusInfo: Record<RdapStatusType, string> = {
"validated": "Signifies that the data of the object instance has been found to be accurate. This type of status is usually found on entity object instances to note the validity of identifying contact information.",
"renew prohibited": "Renewal or reregistration of the object instance is forbidden.",
validated:
"Signifies that the data of the object instance has been found to be accurate. This type of status is usually found on entity object instances to note the validity of identifying contact information.",
"renew prohibited":
"Renewal or reregistration of the object instance is forbidden.",
"update prohibited": "Updates to the object instance are forbidden.",
"transfer prohibited": "Transfers of the registration from one registrar to another are forbidden. This type of status normally applies to DNR domain names.",
"delete prohibited": "Deletion of the registration of the object instance is forbidden. This type of status normally applies to DNR domain names.",
"proxy": "The registration of the object instance has been performed by a third party. This is most commonly applied to entities.",
"private": "The information of the object instance is not designated for public consumption. This is most commonly applied to entities.",
"removed": "Some of the information of the object instance has not been made available and has been removed. This is most commonly applied to entities.",
"obscured": "Some of the information of the object instance has been altered for the purposes of not readily revealing the actual information of the object instance. This is most commonly applied to entities.",
"associated": "The object instance is associated with other object instances in the registry. This is most commonly used to signify that a nameserver is associated with a domain or that an entity is associated with a network resource or domain.",
"active": "The object instance is in use. For domain names, it signifies that the domain name is published in DNS. For network and autnum registrations it signifies that they are allocated or assigned for use in operational networks. This maps to the Extensible Provisioning Protocol (EPP) [RFC5730] 'OK' status.",
"inactive": "The object instance is not in use. See 'active'.",
"locked": "Changes to the object instance cannot be made, including the association of other object instances.",
"pending create": "A request has been received for the creation of the object instance but this action is not yet complete.",
"pending renew": "A request has been received for the renewal of the object instance but this action is not yet complete.",
"pending transfer": "A request has been received for the transfer of the object instance but this action is not yet complete.",
"pending update": "A request has been received for the update or modification of the object instance but this action is not yet complete.",
"pending delete": "A request has been received for the deletion or removal of the object instance but this action is not yet complete. For domains, this might mean that the name is no longer published in DNS but has not yet been purged from the registry database.",
"add period": "This grace period is provided after the initial registration of the object. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the registration. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'addPeriod' status.",
"auto renew period": "This grace period is provided after an object registration period expires and is extended (renewed) automatically by the server. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the auto renewal. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'autoRenewPeriod' status.",
"client delete prohibited": "The client requested that requests to delete the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientDeleteProhibited' status.",
"client hold": "The client requested that the DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'clientHold' status.",
"client renew prohibited": "The client requested that requests to renew the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'clientRenewProhibited' status.",
"client transfer prohibited": "The client requested that requests to transfer the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientTransferProhibited' status.",
"client update prohibited": "The client requested that requests to update the object (other than to remove this status) MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientUpdateProhibited' status.",
"pending restore": "An object is in the process of being restored after being in the redemption period state. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'pendingRestore' status.",
"redemption period": "A delete has been received, but the object has not yet been purged because an opportunity exists to restore the object and abort the deletion process. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'redemptionPeriod' status.",
"renew period": "This grace period is provided after an object registration period is explicitly extended (renewed) by the client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the renewal. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'renewPeriod' status.",
"server delete prohibited": "The server set the status so that requests to delete the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverDeleteProhibited' status.",
"server renew prohibited": "The server set the status so that requests to renew the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverRenewProhibited' status.",
"server transfer prohibited": "The server set the status so that requests to transfer the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverTransferProhibited' status.",
"server update prohibited": "The server set the status so that requests to update the object (other than to remove this status) MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverUpdateProhibited' status.",
"server hold": "The server set the status so that DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverHold' status.",
"transfer period": "This grace period is provided after the successful transfer of object registration sponsorship from one client to another client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the transfer. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'transferPeriod' status."
"transfer prohibited":
"Transfers of the registration from one registrar to another are forbidden. This type of status normally applies to DNR domain names.",
"delete prohibited":
"Deletion of the registration of the object instance is forbidden. This type of status normally applies to DNR domain names.",
proxy:
"The registration of the object instance has been performed by a third party. This is most commonly applied to entities.",
private:
"The information of the object instance is not designated for public consumption. This is most commonly applied to entities.",
removed:
"Some of the information of the object instance has not been made available and has been removed. This is most commonly applied to entities.",
obscured:
"Some of the information of the object instance has been altered for the purposes of not readily revealing the actual information of the object instance. This is most commonly applied to entities.",
associated:
"The object instance is associated with other object instances in the registry. This is most commonly used to signify that a nameserver is associated with a domain or that an entity is associated with a network resource or domain.",
active:
"The object instance is in use. For domain names, it signifies that the domain name is published in DNS. For network and autnum registrations it signifies that they are allocated or assigned for use in operational networks. This maps to the Extensible Provisioning Protocol (EPP) [RFC5730] 'OK' status.",
inactive: "The object instance is not in use. See 'active'.",
locked:
"Changes to the object instance cannot be made, including the association of other object instances.",
"pending create":
"A request has been received for the creation of the object instance but this action is not yet complete.",
"pending renew":
"A request has been received for the renewal of the object instance but this action is not yet complete.",
"pending transfer":
"A request has been received for the transfer of the object instance but this action is not yet complete.",
"pending update":
"A request has been received for the update or modification of the object instance but this action is not yet complete.",
"pending delete":
"A request has been received for the deletion or removal of the object instance but this action is not yet complete. For domains, this might mean that the name is no longer published in DNS but has not yet been purged from the registry database.",
"add period":
"This grace period is provided after the initial registration of the object. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the registration. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'addPeriod' status.",
"auto renew period":
"This grace period is provided after an object registration period expires and is extended (renewed) automatically by the server. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the auto renewal. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'autoRenewPeriod' status.",
"client delete prohibited":
"The client requested that requests to delete the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientDeleteProhibited' status.",
"client hold":
"The client requested that the DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'clientHold' status.",
"client renew prohibited":
"The client requested that requests to renew the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'clientRenewProhibited' status.",
"client transfer prohibited":
"The client requested that requests to transfer the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientTransferProhibited' status.",
"client update prohibited":
"The client requested that requests to update the object (other than to remove this status) MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'clientUpdateProhibited' status.",
"pending restore":
"An object is in the process of being restored after being in the redemption period state. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'pendingRestore' status.",
"redemption period":
"A delete has been received, but the object has not yet been purged because an opportunity exists to restore the object and abort the deletion process. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'redemptionPeriod' status.",
"renew period":
"This grace period is provided after an object registration period is explicitly extended (renewed) by the client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the renewal. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'renewPeriod' status.",
"server delete prohibited":
"The server set the status so that requests to delete the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverDeleteProhibited' status.",
"server renew prohibited":
"The server set the status so that requests to renew the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverRenewProhibited' status.",
"server transfer prohibited":
"The server set the status so that requests to transfer the object MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverTransferProhibited' status.",
"server update prohibited":
"The server set the status so that requests to update the object (other than to remove this status) MUST be rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping [RFC5733] 'serverUpdateProhibited' status.",
"server hold":
"The server set the status so that DNS delegation information MUST NOT be published for the object. This maps to the Extensible Provisioning Protocol (EPP) Domain Name Mapping [RFC5731] 'serverHold' status.",
"transfer period":
"This grace period is provided after the successful transfer of object registration sponsorship from one client to another client. If the object is deleted by the client during this period, the server provides a credit to the client for the cost of the transfer. This maps to the Domain Registry Grace Period Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] 'transferPeriod' status.",
};
// list of RDAP bootstrap registry URLs
export const registryURLs: Record<RootRegistryType, string> = {
"autnum": "https://data.iana.org/rdap/asn.json",
"domain": "https://data.iana.org/rdap/dns.json",
"ip4": "https://data.iana.org/rdap/ipv4.json",
"ip6": "https://data.iana.org/rdap/ipv6.json",
"entity": "https://data.iana.org/rdap/object-tags.json",
autnum: "https://data.iana.org/rdap/asn.json",
domain: "https://data.iana.org/rdap/dns.json",
ip4: "https://data.iana.org/rdap/ipv4.json",
ip6: "https://data.iana.org/rdap/ipv6.json",
entity: "https://data.iana.org/rdap/object-tags.json",
};
export const placeholders: Record<TargetType, string> = {
'ip4': '192.168.0.1/16',
'ip6': 'TODO: Complete this placeholder',
'autnum': '65535',
'entity': 'ABC123-EXAMPLE',
'url': 'https://rdap.org/domain/example.com',
'tld': 'example',
'registrar': '9999',
'json': '{ (paste JSON) }',
'domain': 'example.com'
}
ip4: "192.168.0.1/16",
ip6: "TODO: Complete this placeholder",
autnum: "65535",
entity: "ABC123-EXAMPLE",
url: "https://rdap.org/domain/example.com",
tld: "example",
registrar: "9999",
json: "{ (paste JSON) }",
domain: "example.com",
};

6
src/env/client.mjs vendored
View File

@@ -5,7 +5,7 @@ const _clientEnv = clientSchema.safeParse(clientEnv);
export const formatErrors = (
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
errors,
errors
) =>
Object.entries(errors)
.map(([name, value]) => {
@@ -17,7 +17,7 @@ export const formatErrors = (
if (!_clientEnv.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(_clientEnv.error.format()),
...formatErrors(_clientEnv.error.format())
);
throw new Error("Invalid environment variables");
}
@@ -25,7 +25,7 @@ if (!_clientEnv.success) {
for (let key of Object.keys(_clientEnv.data)) {
if (!key.startsWith("NEXT_PUBLIC_")) {
console.warn(
`❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`,
`❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`
);
throw new Error("Invalid public environment variable name");

2
src/env/server.mjs vendored
View File

@@ -11,7 +11,7 @@ const _serverEnv = serverSchema.safeParse(serverEnv);
if (!_serverEnv.success) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(_serverEnv.error.format()),
...formatErrors(_serverEnv.error.format())
);
throw new Error("Invalid environment variables");
}

View File

@@ -2,7 +2,7 @@ import type {SyntheticEvent} from "react";
export function truthy(value: string | null | undefined) {
if (value == undefined) return false;
return value.toLowerCase() == 'true' || value == '1';
return value.toLowerCase() == "true" || value == "1";
}
export function onPromise<T>(promise: (event: SyntheticEvent) => Promise<T>) {
@@ -22,10 +22,12 @@ export function onPromise<T>(promise: (event: SyntheticEvent) => Promise<T>) {
* @param maxLength A positive number representing the maximum length the input string should be.
* @param ellipsis A string representing what should be placed on the end when the max length is hit.
*/
export function truncated(input: string, maxLength: number, ellipsis = '...') {
if (maxLength <= 0) return '';
export function truncated(input: string, maxLength: number, ellipsis = "...") {
if (maxLength <= 0) return "";
if (input.length <= maxLength) return input;
return input.substring(0, Math.max(0, maxLength - ellipsis.length)) + ellipsis;
return (
input.substring(0, Math.max(0, maxLength - ellipsis.length)) + ellipsis
);
}
/**

View File

@@ -1,8 +1,21 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { domainMatchPredicate, getBestURL, getType } from "@/rdap";
import type {AutonomousNumber, Domain, IpNetwork, Register, RootRegistryType, TargetType} from "@/types";
import type {
AutonomousNumber,
Domain,
IpNetwork,
Register,
RootRegistryType,
TargetType,
} from "@/types";
import { registryURLs } from "@/constants";
import {AutonomousNumberSchema, DomainSchema, IpNetworkSchema, RegisterSchema, RootRegistryEnum} from "@/schema";
import {
AutonomousNumberSchema,
DomainSchema,
IpNetworkSchema,
RegisterSchema,
RootRegistryEnum,
} from "@/schema";
import { truncated } from "@/helpers";
import type { ZodSchema } from "zod";
import type { ParsedGeneric } from "@/components/lookup/Generic";
@@ -10,45 +23,56 @@ import type {ParsedGeneric} from "@/components/lookup/Generic";
export type WarningHandler = (warning: { message: string }) => void;
const useLookup = (warningHandler?: WarningHandler) => {
const registryDataRef = useRef<Record<RootRegistryType, Register | null>>({} as Record<TargetType, Register>)
const registryDataRef = useRef<Record<RootRegistryType, Register | null>>(
{} as Record<TargetType, Register>
);
const [error, setError] = useState<string | null>(null);
const [target, setTarget] = useState<string>("");
const uriType = useMemo<TargetType | 'unknown'>(function () {
return getType(target) ?? 'unknown';
}, [target]);
const uriType = useMemo<TargetType | "unknown">(
function () {
return getType(target) ?? "unknown";
},
[target]
);
// Fetch & load a specific registry's data into memory.
async function loadBootstrap(type: RootRegistryType, force = false) {
// Early preload exit condition
if (registryDataRef.current[type] != null && !force)
return;
if (registryDataRef.current[type] != null && !force) return;
// Fetch the bootstrapping file from the registry
const response = await fetch(registryURLs[type]);
if (response.status != 200)
throw new Error(`Error: ${response.statusText}`)
throw new Error(`Error: ${response.statusText}`);
// Parse it, so we don't make any false assumptions during development & while maintaining the tool.
const parsedRegister = RegisterSchema.safeParse(await response.json());
if (!parsedRegister.success)
throw new Error(`Could not parse IANA bootstrap response (type: ${type}).`)
throw new Error(
`Could not parse IANA bootstrap response (type: ${type}).`
);
// Set it in state so we can use it.
registryDataRef.current = {
...registryDataRef.current,
[type]: parsedRegister.data
}
[type]: parsedRegister.data,
};
}
function getRegistryURL(type: RootRegistryType, lookupTarget: string): string {
function getRegistryURL(
type: RootRegistryType,
lookupTarget: string
): string {
const bootstrap = registryDataRef.current[type];
if (bootstrap == null) throw new Error(`Cannot acquire RDAP URL without bootstrap data for ${type} lookup.`)
if (bootstrap == null)
throw new Error(
`Cannot acquire RDAP URL without bootstrap data for ${type} lookup.`
);
let url: string | null = null;
typeSwitch:
switch (type) {
typeSwitch: switch (type) {
case "domain":
for (const bootstrapItem of bootstrap.services) {
if (bootstrapItem[0].some(domainMatchPredicate(lookupTarget))) {
@@ -56,56 +80,62 @@ const useLookup = (warningHandler?: WarningHandler) => {
break typeSwitch;
}
}
throw new Error(`No matching domain found.`)
throw new Error(`No matching domain found.`);
case "ip4":
throw new Error(`No matching ip4 found.`)
throw new Error(`No matching ip4 found.`);
case "ip6":
throw new Error(`No matching ip6 found.`)
throw new Error(`No matching ip6 found.`);
case "entity":
throw new Error(`No matching entity found.`)
throw new Error(`No matching entity found.`);
case "autnum":
throw new Error(`No matching autnum found.`)
throw new Error(`No matching autnum found.`);
default:
throw new Error("Invalid lookup target provided.")
throw new Error("Invalid lookup target provided.");
}
if (url == null) throw new Error('No lookup target was resolved.')
if (url == null) throw new Error("No lookup target was resolved.");
return `${url}${type}/${lookupTarget}`;
}
useEffect(() => {
const preload = async () => {
if (uriType === 'unknown') return;
if (uriType === "unknown") return;
const registryUri = RootRegistryEnum.safeParse(uriType);
if (!registryUri.success) return;
console.log({registryData: registryDataRef.current, registryUri: registryUri.data});
console.log({
registryData: registryDataRef.current,
registryUri: registryUri.data,
});
if (registryDataRef.current[registryUri.data] != null) return;
try {
await loadBootstrap(registryUri.data);
} catch (e) {
if (warningHandler != undefined) {
const message = e instanceof Error ? `(${truncated(e.message, 15)})` : '.';
const message =
e instanceof Error ? `(${truncated(e.message, 15)})` : ".";
warningHandler({
message: `Failed to preload registry${message}`
})
}
message: `Failed to preload registry${message}`,
});
}
}
};
preload().catch(console.error);
}, [target]);
async function getAndParse<T>(url: string, schema: ZodSchema): Promise<T | undefined> {
async function getAndParse<T>(
url: string,
schema: ZodSchema
): Promise<T | undefined> {
const response = await fetch(url);
if (response.status == 200)
return schema.parse(await response.json()) as T
if (response.status == 200) return schema.parse(await response.json()) as T;
}
async function submitInternal(): Promise<ParsedGeneric | undefined> {
if (target == null)
throw new Error("A target must be given in order to execute a lookup.")
throw new Error("A target must be given in order to execute a lookup.");
const targetType = getType(target);
@@ -114,7 +144,7 @@ const useLookup = (warningHandler?: WarningHandler) => {
case "ip4": {
await loadBootstrap("ip4");
const url = getRegistryURL(targetType, target);
return await getAndParse<IpNetwork>(url, IpNetworkSchema)
return await getAndParse<IpNetwork>(url, IpNetworkSchema);
}
case "ip6": {
await loadBootstrap("ip6");
@@ -132,13 +162,13 @@ const useLookup = (warningHandler?: WarningHandler) => {
return await getAndParse<AutonomousNumber>(url, AutonomousNumberSchema);
}
case null:
throw new Error("The type could not be detected given the target.")
throw new Error("The type could not be detected given the target.");
case "url":
case "tld":
case "registrar":
case "json":
default:
throw new Error("The type detected has not been implemented.")
throw new Error("The type detected has not been implemented.");
}
}
@@ -146,7 +176,7 @@ const useLookup = (warningHandler?: WarningHandler) => {
try {
const response = await submitInternal();
if (response == undefined)
throw new Error("Internal submission failed to yield any data.")
throw new Error("Internal submission failed to yield any data.");
setError(null);
return response;
@@ -158,6 +188,6 @@ const useLookup = (warningHandler?: WarningHandler) => {
}
return { error, setTarget, submit, currentType: uriType };
}
};
export default useLookup;

View File

@@ -4,11 +4,11 @@ import type {TargetType} from "@/types";
// let elementCounter = 123456;
const cardTitles = {
"domain": "Domain Name",
domain: "Domain Name",
"ip network": "IP Network",
"nameserver": "Nameserver",
"entity": "Entity",
"autnum": "AS Number",
nameserver: "Nameserver",
entity: "Entity",
autnum: "AS Number",
};
export function domainMatchPredicate(domain: string): (tld: string) => boolean {
@@ -46,9 +46,8 @@ export function ipMatch(prefix: string, ip: string) {
// return the first HTTPS url, or the first URL
export function getBestURL(urls: string[]): string {
urls.forEach((url) => {
if (url.startsWith('https://'))
return url;
})
if (url.startsWith("https://")) return url;
});
return urls[0]!;
}
@@ -768,13 +767,15 @@ const URIPatterns: [RegExp, TargetType][] = [
[/^https?:/, "url"],
[/^{/, "json"],
[/^\.\w+$/, "tld"],
[/[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?/, "domain"],
[
/[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?/,
"domain",
],
];
export function getType(value: string): TargetType | null {
for (const [pattern, type] of URIPatterns) {
if (pattern.test(value))
return type;
if (pattern.test(value)) return type;
}
return null;
}

View File

@@ -1,54 +1,111 @@
import { z } from "zod";
export const ObjectTypeEnum = z.enum(['ip', 'autnum', 'entity', 'url', 'tld', 'registrar', 'json', 'domain'])
export const RootRegistryEnum = z.enum(['autnum', 'domain', 'ip4', 'ip6', 'entity'])
export const StatusEnum = z.enum(["validated", "renew prohibited", "update prohibited", "transfer prohibited", "delete prohibited", "proxy", "private", "removed", "obscured", "associated", "active", "inactive", "locked", "pending create", "pending renew", "pending transfer", "pending update", "pending delete", "add period", "auto renew period", "client delete prohibited", "client hold", "client renew prohibited", "client transfer prohibited", "client update prohibited", "pending restore", "redemption period", "renew period", "server delete prohibited", "server renew prohibited", "server transfer prohibited", "server update prohibited", "server hold", "transfer period"])
export const ObjectTypeEnum = z.enum([
"ip",
"autnum",
"entity",
"url",
"tld",
"registrar",
"json",
"domain",
]);
export const RootRegistryEnum = z.enum([
"autnum",
"domain",
"ip4",
"ip6",
"entity",
]);
export const StatusEnum = z.enum([
"validated",
"renew prohibited",
"update prohibited",
"transfer prohibited",
"delete prohibited",
"proxy",
"private",
"removed",
"obscured",
"associated",
"active",
"inactive",
"locked",
"pending create",
"pending renew",
"pending transfer",
"pending update",
"pending delete",
"add period",
"auto renew period",
"client delete prohibited",
"client hold",
"client renew prohibited",
"client transfer prohibited",
"client update prohibited",
"pending restore",
"redemption period",
"renew period",
"server delete prohibited",
"server renew prohibited",
"server transfer prohibited",
"server update prohibited",
"server hold",
"transfer period",
]);
export const LinkSchema = z.object({
value: z.string().optional(),
rel: z.string(),
href: z.string(),
type: z.string()
})
type: z.string(),
});
export const EntitySchema = z.object({
objectClassName: z.literal('entity'),
objectClassName: z.literal("entity"),
handle: z.string(),
roles: z.array(z.string()),
publicIds: z.array(z.object({
publicIds: z
.array(
z.object({
type: z.string(),
identifier: z.string(),
})).optional()
})
)
.optional(),
});
export const NameserverSchema = z.object({
objectClassName: z.literal('nameserver'),
ldhName: z.string()
})
objectClassName: z.literal("nameserver"),
ldhName: z.string(),
});
export const EventSchema = z.object({
eventAction: z.string(),
eventActor: z.string().optional(),
eventDate: z.string()
})
eventDate: z.string(),
});
export const NoticeSchema = z.object({
title: z.string().optional(),
description: z.string().array(),
links: z.array(z.object({
links: z
.array(
z.object({
href: z.string(),
type: z.string()
})).optional()
type: z.string(),
})
)
.optional(),
});
export type Notice = z.infer<typeof NoticeSchema>;
export const IpNetworkSchema = z.object({
objectClassName: z.literal('ip network'),
objectClassName: z.literal("ip network"),
handle: z.string(),
startAddress: z.string(),
endAddress: z.string(),
ipVersion: z.enum(['v4', 'v6']),
ipVersion: z.enum(["v4", "v6"]),
name: z.string(),
type: z.string(),
country: z.string(),
@@ -58,12 +115,11 @@ export const IpNetworkSchema = z.object({
remarks: z.any(),
links: z.any(),
port43: z.any().optional(),
events: z.array(EventSchema)
})
events: z.array(EventSchema),
});
export const AutonomousNumberSchema = z.object({
objectClassName: z.literal('autnum'),
objectClassName: z.literal("autnum"),
handle: z.string(),
startAutnum: z.number().positive(), // TODO: 32bit
endAutnum: z.number().positive(), // TODO: 32bit
@@ -74,11 +130,11 @@ export const AutonomousNumberSchema = z.object({
events: z.array(EventSchema),
entities: z.array(EntitySchema),
roles: z.array(z.string()),
links: z.array(LinkSchema)
})
links: z.array(LinkSchema),
});
export const DomainSchema = z.object({
objectClassName: z.literal('domain'),
objectClassName: z.literal("domain"),
handle: z.string(),
ldhName: z.string(),
unicodeName: z.string().optional(),
@@ -91,21 +147,21 @@ export const DomainSchema = z.object({
rdapConformance: z.string().array(), // TODO: Complete
notices: z.array(NoticeSchema),
network: IpNetworkSchema.optional(),
})
});
const RegistrarSchema = z.tuple([
z.array(z.string()).min(1),
z.array(z.string()).min(1)
]).or(z.tuple([
const RegistrarSchema = z
.tuple([z.array(z.string()).min(1), z.array(z.string()).min(1)])
.or(
z.tuple([
z.array(z.string()).min(1),
z.array(z.string()).min(1),
z.array(z.string()).min(1)
]))
z.array(z.string()).min(1),
])
);
export const RegisterSchema = z.object({
description: z.string(),
publication: z.string(),
services: z.array(RegistrarSchema),
version: z.string()
version: z.string(),
});

View File

@@ -10,12 +10,12 @@ import type {
ObjectTypeEnum,
RegisterSchema,
StatusEnum,
RootRegistryEnum
RootRegistryEnum,
} from "@/schema";
export type ObjectType = z.infer<typeof ObjectTypeEnum>
export type ObjectType = z.infer<typeof ObjectTypeEnum>;
export type RootRegistryType = z.infer<typeof RootRegistryEnum>;
export type TargetType = Exclude<ObjectType, 'ip'> | 'ip4' | 'ip6';
export type TargetType = Exclude<ObjectType, "ip"> | "ip4" | "ip6";
export type RdapStatusType = z.infer<typeof StatusEnum>;
export type Link = z.infer<typeof LinkSchema>;

View File

@@ -5,8 +5,8 @@ module.exports = {
extend: {
colors: {
zinc: {
850: "#1D1D20"
}
850: "#1D1D20",
},
},
},
},

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -21,22 +17,10 @@
"noUncheckedIndexedAccess": true,
"baseUrl": "./src/",
"paths": {
"@/config/*": [
"../config/*"
],
"@/*": [
"./*"
]
"@/config/*": ["../config/*"],
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.cjs",
"**/*.mjs"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"],
"exclude": ["node_modules"]
}