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

@@ -2,4 +2,4 @@
A private RDAP query client built with React & Next.js. A private RDAP query client built with React & Next.js.
[xevion-github]: https://github.com/Xevion [xevion-github]: https://github.com/Xevion

View File

@@ -7,11 +7,11 @@
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const config = { const config = {
reactStrictMode: true, reactStrictMode: true,
swcMinify: true, swcMinify: true,
i18n: { i18n: {
locales: ["en"], locales: ["en"],
defaultLocale: "en", defaultLocale: "en",
} },
}; };
export default config; export default config;

View File

@@ -11,25 +11,25 @@
* @returns {number} The relative position of the value to the range as -1, 0 or 1. * @returns {number} The relative position of the value to the range as -1, 0 or 1.
*/ */
function compareASN(value: number, range: string): number { 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(start)) return -1;
if (value > parseInt(end)) return 1; if (value > parseInt(end)) return 1;
return 0; return 0;
} }
/** /**
* Find the range in which a given ASN exists via binary search. If not found, -1 is used. * Find the range in which a given ASN exists via binary search. If not found, -1 is used.
*/ */
export function findASN(asn: number, ranges: string[]) { export function findASN(asn: number, ranges: string[]) {
let start = 0; let start = 0;
let end = ranges.length - 1; let end = ranges.length - 1;
while (start <= end) { while (start <= end) {
const mid = Math.floor((start + end) / 2); const mid = Math.floor((start + end) / 2);
const comparison = compareASN(asn, ranges[mid] as string); const comparison = compareASN(asn, ranges[mid] as string);
if (comparison == 0) return mid; // Success case if (comparison == 0) return mid; // Success case
if (comparison == -1) end = mid - 1; if (comparison == -1) end = mid - 1;
else start = mid + 1; else start = mid + 1;
} }
return -1; // Failure case return -1; // Failure case
} }

View File

@@ -1,30 +1,36 @@
import type {FunctionComponent} from "react"; import type { FunctionComponent } from "react";
import {useBoolean} from "usehooks-ts"; import { useBoolean } from "usehooks-ts";
import {format, formatDistanceToNow} from "date-fns"; import { format, formatDistanceToNow } from "date-fns";
type DynamicDateProps = { type DynamicDateProps = {
value: Date | number; value: Date | number;
absoluteFormat?: string; absoluteFormat?: string;
} };
/** /**
* A component for a toggleable switch between the absolute & human-relative date. * A component for a toggleable switch between the absolute & human-relative date.
* @param value The date to be displayed, the Date value, or * @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. * @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> = ({
const {value: showAbsolute, toggle: toggleFormat} = useBoolean(true); value,
absoluteFormat,
}) => {
const { value: showAbsolute, toggle: toggleFormat } = useBoolean(true);
const date = new Date(value); const date = new Date(value);
return ( return (
<button onClick={toggleFormat}> <button onClick={toggleFormat}>
<span title={date.toISOString()}> <span title={date.toISOString()}>
{showAbsolute {showAbsolute
? format(date, absoluteFormat ?? "LLL do, y HH:mm:ss") ? format(date, absoluteFormat ?? "LLL do, y HH:mm:ss")
: formatDistanceToNow(date, {includeSeconds: true, addSuffix: true})} : formatDistanceToNow(date, {
</span> includeSeconds: true,
</button> addSuffix: true,
) })}
} </span>
</button>
);
};
export default DynamicDate; export default DynamicDate;

View File

@@ -1,19 +1,26 @@
import type {FunctionComponent, ReactFragment, ReactNode} from "react"; import type { FunctionComponent, ReactFragment, ReactNode } from "react";
import React from "react"; import React from "react";
import {classNames} from "@/helpers"; import { classNames } from "@/helpers";
type PropertyProps = { type PropertyProps = {
title: string | ReactNode | ReactFragment; title: string | ReactNode | ReactFragment;
children: string | ReactNode | ReactFragment; children: string | ReactNode | ReactFragment;
titleClass?: string; titleClass?: string;
valueClass?: string; valueClass?: string;
}; };
const Property: FunctionComponent<PropertyProps> = ({title, children, titleClass, valueClass}) => { const Property: FunctionComponent<PropertyProps> = ({
return <> title,
<dt className={titleClass}>{title}:</dt> children,
<dd className={classNames("mt-2 mb-2 ml-6", valueClass)}>{children}</dd> titleClass,
valueClass,
}) => {
return (
<>
<dt className={titleClass}>{title}:</dt>
<dd className={classNames("mt-2 mb-2 ml-6", valueClass)}>{children}</dd>
</> </>
} );
};
export default Property; export default Property;

View File

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

View File

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

View File

@@ -1,34 +1,46 @@
import type {FunctionComponent} from "react"; import type { FunctionComponent } from "react";
import DomainCard from "@/components/lookup/DomainCard"; 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 = { export type ObjectProps = {
data: ParsedGeneric; data: ParsedGeneric;
}; };
const Generic: FunctionComponent<ObjectProps> = ({data}: ObjectProps) => { const Generic: FunctionComponent<ObjectProps> = ({ data }: ObjectProps) => {
switch (data.objectClassName) { switch (data.objectClassName) {
case "domain": case "domain":
return <DomainCard data={data}/> return <DomainCard data={data} />;
case "autnum": case "autnum":
case "entity": case "entity":
case "ip network": case "ip network":
case "nameserver": case "nameserver":
default: default:
return <div className="card my-2"> return (
<div className="card-header">Not implemented. ( <div className="card my-2">
<pre>{data.objectClassName ?? "null"}</pre> <div className="card-header">
) Not implemented. (<pre>{data.objectClassName ?? "null"}</pre>)
</div> </div>
</div> </div>
} );
}
// const title: string = (data.unicodeName ?? data.ldhName ?? data.handle)?.toUpperCase() ?? "Response"; // const title: string = (data.unicodeName ?? data.ldhName ?? data.handle)?.toUpperCase() ?? "Response";
// return <div className="card"> // return <div className="card">
// <div className="card-header">{title}</div> // <div className="card-header">{title}</div>
// {objectFragment} // {objectFragment}
// </div> // </div>
} };
export default Generic; export default Generic;

View File

@@ -1,60 +1,92 @@
// see https://www.iana.org/assignments/rdap-json-values // see https://www.iana.org/assignments/rdap-json-values
import type {RdapStatusType, RootRegistryType, TargetType} from "@/types"; import type { RdapStatusType, RootRegistryType, TargetType } from "@/types";
export const rdapStatusInfo: Record<RdapStatusType, string> = { 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.", validated:
"renew prohibited": "Renewal or reregistration of the object instance is forbidden.", "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.",
"update prohibited": "Updates to the object instance are forbidden.", "renew prohibited":
"transfer prohibited": "Transfers of the registration from one registrar to another are forbidden. This type of status normally applies to DNR domain names.", "Renewal or reregistration of the object instance is forbidden.",
"delete prohibited": "Deletion of the registration of the object instance is forbidden. This type of status normally applies to DNR domain names.", "update prohibited": "Updates to the object instance are forbidden.",
"proxy": "The registration of the object instance has been performed by a third party. This is most commonly applied to entities.", "transfer prohibited":
"private": "The information of the object instance is not designated for public consumption. This is most commonly applied to entities.", "Transfers of the registration from one registrar to another are forbidden. This type of status normally applies to DNR domain names.",
"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.", "delete prohibited":
"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.", "Deletion of the registration of the object instance is forbidden. This type of status normally applies to DNR domain names.",
"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.", proxy:
"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.", "The registration of the object instance has been performed by a third party. This is most commonly applied to entities.",
"inactive": "The object instance is not in use. See 'active'.", private:
"locked": "Changes to the object instance cannot be made, including the association of other object instances.", "The information of the object instance is not designated for public consumption. This is most commonly applied to entities.",
"pending create": "A request has been received for the creation of the object instance but this action is not yet complete.", removed:
"pending renew": "A request has been received for the renewal of the object instance but this action is not yet complete.", "Some of the information of the object instance has not been made available and has been removed. This is most commonly applied to entities.",
"pending transfer": "A request has been received for the transfer of the object instance but this action is not yet complete.", obscured:
"pending update": "A request has been received for the update or modification of the object instance but this action is not yet complete.", "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.",
"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.", associated:
"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.", "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.",
"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.", active:
"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.", "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.",
"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.", inactive: "The object instance is not in use. See 'active'.",
"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.", locked:
"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.", "Changes to the object instance cannot be made, including the association of other object instances.",
"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 create":
"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.", "A request has been received for the creation of the object instance but this action is not yet complete.",
"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.", "pending renew":
"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.", "A request has been received for the renewal of the object instance but this action is not yet complete.",
"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.", "pending transfer":
"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.", "A request has been received for the transfer of the object instance but this action is not yet complete.",
"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.", "pending update":
"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.", "A request has been received for the update or modification of the object instance but this action is not yet complete.",
"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.", "pending delete":
"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." "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 // list of RDAP bootstrap registry URLs
export const registryURLs: Record<RootRegistryType, string> = { export const registryURLs: Record<RootRegistryType, string> = {
"autnum": "https://data.iana.org/rdap/asn.json", autnum: "https://data.iana.org/rdap/asn.json",
"domain": "https://data.iana.org/rdap/dns.json", domain: "https://data.iana.org/rdap/dns.json",
"ip4": "https://data.iana.org/rdap/ipv4.json", ip4: "https://data.iana.org/rdap/ipv4.json",
"ip6": "https://data.iana.org/rdap/ipv6.json", ip6: "https://data.iana.org/rdap/ipv6.json",
"entity": "https://data.iana.org/rdap/object-tags.json", entity: "https://data.iana.org/rdap/object-tags.json",
}; };
export const placeholders: Record<TargetType, string> = { export const placeholders: Record<TargetType, string> = {
'ip4': '192.168.0.1/16', ip4: "192.168.0.1/16",
'ip6': 'TODO: Complete this placeholder', ip6: "TODO: Complete this placeholder",
'autnum': '65535', autnum: "65535",
'entity': 'ABC123-EXAMPLE', entity: "ABC123-EXAMPLE",
'url': 'https://rdap.org/domain/example.com', url: "https://rdap.org/domain/example.com",
'tld': 'example', tld: "example",
'registrar': '9999', registrar: "9999",
'json': '{ (paste JSON) }', json: "{ (paste JSON) }",
'domain': 'example.com' domain: "example.com",
} };

6
src/env/client.mjs vendored
View File

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

View File

@@ -1,18 +1,18 @@
import type {SyntheticEvent} from "react"; import type { SyntheticEvent } from "react";
export function truthy(value: string | null | undefined) { export function truthy(value: string | null | undefined) {
if (value == undefined) return false; 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>) { export function onPromise<T>(promise: (event: SyntheticEvent) => Promise<T>) {
return (event: SyntheticEvent) => { return (event: SyntheticEvent) => {
if (promise) { if (promise) {
promise(event).catch((error) => { promise(event).catch((error) => {
console.log("Unexpected error", error); console.log("Unexpected error", error);
}); });
} }
}; };
} }
/** /**
@@ -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 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. * @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 = '...') { export function truncated(input: string, maxLength: number, ellipsis = "...") {
if (maxLength <= 0) return ''; if (maxLength <= 0) return "";
if (input.length <= maxLength) return input; 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
);
} }
/** /**
@@ -33,5 +35,5 @@ export function truncated(input: string, maxLength: number, ellipsis = '...') {
* @param classes * @param classes
*/ */
export function classNames(...classes: (string | null | undefined)[]) { export function classNames(...classes: (string | null | undefined)[]) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
} }

View File

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

View File

@@ -1,22 +1,22 @@
import type {TargetType} from "@/types"; import type { TargetType } from "@/types";
// keeps track of the elements we've created so we can assign a unique ID // keeps track of the elements we've created so we can assign a unique ID
// let elementCounter = 123456; // let elementCounter = 123456;
const cardTitles = { const cardTitles = {
"domain": "Domain Name", domain: "Domain Name",
"ip network": "IP Network", "ip network": "IP Network",
"nameserver": "Nameserver", nameserver: "Nameserver",
"entity": "Entity", entity: "Entity",
"autnum": "AS Number", autnum: "AS Number",
}; };
export function domainMatchPredicate(domain: string): (tld: string) => boolean { export function domainMatchPredicate(domain: string): (tld: string) => boolean {
return (tld) => domainMatch(tld, domain); return (tld) => domainMatch(tld, domain);
} }
export function domainMatch(tld: string, domain: string): boolean { export function domainMatch(tld: string, domain: string): boolean {
return domain.toUpperCase().endsWith(`.${tld.toUpperCase()}`); return domain.toUpperCase().endsWith(`.${tld.toUpperCase()}`);
} }
/* /*
@@ -45,11 +45,10 @@ export function ipMatch(prefix: string, ip: string) {
// return the first HTTPS url, or the first URL // return the first HTTPS url, or the first URL
export function getBestURL(urls: string[]): string { export function getBestURL(urls: string[]): string {
urls.forEach((url) => { urls.forEach((url) => {
if (url.startsWith('https://')) if (url.startsWith("https://")) return url;
return url; });
}) return urls[0]!;
return urls[0]!;
} }
// given a URL, injects that URL into the query input, // given a URL, injects that URL into the query input,
@@ -736,7 +735,7 @@ export function processUnknown(object, dl, toplevel = false) {
} }
*/ */
// given an object, return the "self" URL (if any) // given an object, return the "self" URL (if any)
/* /*
export function getSelfLink(object) { export function getSelfLink(object) {
if (object.links) for (let i = 0; i < object.links.length; i++) if ('self' == object.links[i].rel) return object.links[i].href; if (object.links) for (let i = 0; i < object.links.length; i++) if ('self' == object.links[i].rel) return object.links[i].href;
@@ -762,19 +761,21 @@ export function createRDAPLink(url, title) {
// TODO: Provide full domain, TLD, Ipv4 & Ipv6 validators // TODO: Provide full domain, TLD, Ipv4 & Ipv6 validators
const URIPatterns: [RegExp, TargetType][] = [ const URIPatterns: [RegExp, TargetType][] = [
[/^\d+$/, "autnum"], [/^\d+$/, "autnum"],
[/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/, "ip4"], [/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?\d*$/, "ip4"],
[/^[0-9a-f:]{2,}\/?\d*$/, "ip6"], [/^[0-9a-f:]{2,}\/?\d*$/, "ip6"],
[/^https?:/, "url"], [/^https?:/, "url"],
[/^{/, "json"], [/^{/, "json"],
[/^\.\w+$/, "tld"], [/^\.\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 { export function getType(value: string): TargetType | null {
for (const [pattern, type] of URIPatterns) { for (const [pattern, type] of URIPatterns) {
if (pattern.test(value)) if (pattern.test(value)) return type;
return type; }
} return null;
return null; }
}

View File

@@ -1,111 +1,167 @@
import {z} from "zod"; import { z } from "zod";
export const ObjectTypeEnum = z.enum(['ip', 'autnum', 'entity', 'url', 'tld', 'registrar', 'json', 'domain']) export const ObjectTypeEnum = z.enum([
export const RootRegistryEnum = z.enum(['autnum', 'domain', 'ip4', 'ip6', 'entity']) "ip",
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"]) "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({ export const LinkSchema = z.object({
value: z.string().optional(), value: z.string().optional(),
rel: z.string(), rel: z.string(),
href: z.string(), href: z.string(),
type: z.string() type: z.string(),
}) });
export const EntitySchema = z.object({ export const EntitySchema = z.object({
objectClassName: z.literal('entity'), objectClassName: z.literal("entity"),
handle: z.string(), handle: z.string(),
roles: z.array(z.string()), roles: z.array(z.string()),
publicIds: z.array(z.object({ publicIds: z
.array(
z.object({
type: z.string(), type: z.string(),
identifier: z.string(), identifier: z.string(),
})).optional() })
}) )
.optional(),
});
export const NameserverSchema = z.object({ export const NameserverSchema = z.object({
objectClassName: z.literal('nameserver'), objectClassName: z.literal("nameserver"),
ldhName: z.string() ldhName: z.string(),
}) });
export const EventSchema = z.object({ export const EventSchema = z.object({
eventAction: z.string(), eventAction: z.string(),
eventActor: z.string().optional(), eventActor: z.string().optional(),
eventDate: z.string() eventDate: z.string(),
}) });
export const NoticeSchema = z.object({ export const NoticeSchema = z.object({
title: z.string().optional(), title: z.string().optional(),
description: z.string().array(), description: z.string().array(),
links: z.array(z.object({ links: z
.array(
z.object({
href: z.string(), href: z.string(),
type: z.string() type: z.string(),
})).optional() })
}) )
.optional(),
});
export type Notice = z.infer<typeof NoticeSchema>; export type Notice = z.infer<typeof NoticeSchema>;
export const IpNetworkSchema = z.object({ export const IpNetworkSchema = z.object({
objectClassName: z.literal('ip network'), objectClassName: z.literal("ip network"),
handle: z.string(), handle: z.string(),
startAddress: z.string(), startAddress: z.string(),
endAddress: z.string(), endAddress: z.string(),
ipVersion: z.enum(['v4', 'v6']), ipVersion: z.enum(["v4", "v6"]),
name: z.string(), name: z.string(),
type: z.string(), type: z.string(),
country: z.string(), country: z.string(),
parentHandle: z.string(), parentHandle: z.string(),
status: z.string().array(), status: z.string().array(),
entities: z.array(EntitySchema), entities: z.array(EntitySchema),
remarks: z.any(), remarks: z.any(),
links: z.any(), links: z.any(),
port43: z.any().optional(), port43: z.any().optional(),
events: z.array(EventSchema) events: z.array(EventSchema),
}) });
export const AutonomousNumberSchema = z.object({ export const AutonomousNumberSchema = z.object({
objectClassName: z.literal('autnum'), objectClassName: z.literal("autnum"),
handle: z.string(), handle: z.string(),
startAutnum: z.number().positive(), // TODO: 32bit startAutnum: z.number().positive(), // TODO: 32bit
endAutnum: z.number().positive(), // TODO: 32bit endAutnum: z.number().positive(), // TODO: 32bit
name: z.string(), name: z.string(),
type: z.string(), type: z.string(),
status: z.array(z.string()), status: z.array(z.string()),
country: z.string().length(2), country: z.string().length(2),
events: z.array(EventSchema), events: z.array(EventSchema),
entities: z.array(EntitySchema), entities: z.array(EntitySchema),
roles: z.array(z.string()), roles: z.array(z.string()),
links: z.array(LinkSchema) links: z.array(LinkSchema),
}) });
export const DomainSchema = z.object({ export const DomainSchema = z.object({
objectClassName: z.literal('domain'), objectClassName: z.literal("domain"),
handle: z.string(), handle: z.string(),
ldhName: z.string(), ldhName: z.string(),
unicodeName: z.string().optional(), unicodeName: z.string().optional(),
links: z.array(LinkSchema).optional(), links: z.array(LinkSchema).optional(),
status: z.array(StatusEnum), status: z.array(StatusEnum),
entities: z.array(EntitySchema), entities: z.array(EntitySchema),
events: z.array(EventSchema), events: z.array(EventSchema),
secureDNS: z.any(), // TODO: Complete schema secureDNS: z.any(), // TODO: Complete schema
nameservers: z.array(NameserverSchema), nameservers: z.array(NameserverSchema),
rdapConformance: z.string().array(), // TODO: Complete rdapConformance: z.string().array(), // TODO: Complete
notices: z.array(NoticeSchema), notices: z.array(NoticeSchema),
network: IpNetworkSchema.optional(), network: IpNetworkSchema.optional(),
}) });
const RegistrarSchema = z
const RegistrarSchema = z.tuple([ .tuple([z.array(z.string()).min(1), z.array(z.string()).min(1)])
z.array(z.string()).min(1), .or(
z.array(z.string()).min(1) z.tuple([
]).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), z.array(z.string()).min(1),
z.array(z.string()).min(1) ])
])) );
export const RegisterSchema = z.object({ export const RegisterSchema = z.object({
description: z.string(), description: z.string(),
publication: z.string(), publication: z.string(),
services: z.array(RegistrarSchema), services: z.array(RegistrarSchema),
version: z.string() version: z.string(),
}); });

View File

@@ -1,21 +1,21 @@
import type {z} from "zod"; import type { z } from "zod";
import type { import type {
AutonomousNumberSchema, AutonomousNumberSchema,
DomainSchema, DomainSchema,
EntitySchema, EntitySchema,
EventSchema, EventSchema,
IpNetworkSchema, IpNetworkSchema,
LinkSchema, LinkSchema,
NameserverSchema, NameserverSchema,
ObjectTypeEnum, ObjectTypeEnum,
RegisterSchema, RegisterSchema,
StatusEnum, StatusEnum,
RootRegistryEnum RootRegistryEnum,
} from "@/schema"; } 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 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 RdapStatusType = z.infer<typeof StatusEnum>;
export type Link = z.infer<typeof LinkSchema>; export type Link = z.infer<typeof LinkSchema>;

View File

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

View File

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