import { useState, type FC } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { Loader2 } from "lucide-react"; interface DNSRecord { name: string; type: number; TTL: number; data: string; } interface DNSResponse { Status: number; Answer?: DNSRecord[]; Question?: Array<{ name: string; type: number }>; } const DNS_RECORD_TYPES = [ { value: "1", label: "A", description: "IPv4 Address" }, { value: "28", label: "AAAA", description: "IPv6 Address" }, { value: "5", label: "CNAME", description: "Canonical Name" }, { value: "15", label: "MX", description: "Mail Exchange" }, { value: "2", label: "NS", description: "Name Server" }, { value: "16", label: "TXT", description: "Text Record" }, { value: "6", label: "SOA", description: "Start of Authority" }, { value: "257", label: "CAA", description: "Certification Authority Authorization" }, { value: "12", label: "PTR", description: "Pointer Record" }, { value: "33", label: "SRV", description: "Service Record" }, ]; const getRecordTypeName = (type: number): string => { const record = DNS_RECORD_TYPES.find((r) => r.value === String(type)); return record ? record.label : `TYPE${type}`; }; const Tool: FC = () => { const [domain, setDomain] = useState(""); const [loading, setLoading] = useState(false); const [results, setResults] = useState([]); const [queryTime, setQueryTime] = useState(0); const handleDomainBlur = () => { if (!domain.trim()) return; let input = domain.trim(); let cleanDomain = input; try { // Try to parse as URL const url = new URL(input.startsWith('http') ? input : `https://${input}`); cleanDomain = url.hostname; } catch { // If parsing fails, fallback to manual cleanup cleanDomain = input.replace(/^https?:\/\//, "").split("/")[0].split(":")[0]; } if (cleanDomain !== input) { setDomain(cleanDomain); } }; const queryDNS = async () => { if (!domain.trim()) { toast.error("Please enter a domain name"); return; } setLoading(true); setResults([]); setQueryTime(0); const startTime = performance.now(); try { // Query all record types concurrently const queries = DNS_RECORD_TYPES.map((recordType) => fetch( `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent( domain.trim() )}&type=${recordType.value}`, { headers: { Accept: "application/dns-json", }, } ) .then((response) => response.json()) .then((data: DNSResponse) => { if (data.Status === 0 && data.Answer && data.Answer.length > 0) { return data.Answer; } return []; }) .catch(() => []) ); const allResults = await Promise.all(queries); const endTime = performance.now(); setQueryTime(endTime - startTime); // Merge and deduplicate results const combinedResults = allResults.flat(); if (combinedResults.length > 0) { // Group by record type and deduplicate const uniqueResults = Array.from( new Map( combinedResults.map((record) => [ `${record.name}-${record.type}-${record.data}`, record, ]) ).values() ); setResults(uniqueResults); toast.success(`Query successful, found ${uniqueResults.length} record(s)`); } else { setResults([]); toast.info("No records found"); } } catch (error: unknown) { if (error instanceof Error) { toast.error(`Query failed: ${error.message}`); } else { toast.error("Query failed"); } setResults([]); } finally { setLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !loading) { queryDNS(); } }; return (
setDomain(e.target.value)} onBlur={handleDomainBlur} onKeyPress={handleKeyPress} disabled={loading} /> Will automatically query all DNS record types
{queryTime > 0 && (
Query time: {queryTime.toFixed(2)} ms
)} {results.length > 0 && (
Query Results:
{results.map((record, index) => (
Name:
{record.name}
Type:
{getRecordTypeName(record.type)}
TTL:
{record.TTL} seconds
Data:
{record.data}
))}
)}
); }; export default Tool;