import { createConnection } from 'net'; import type { VercelRequest, VercelResponse } from '@vercel/node'; // TLD to WHOIS server mapping const WHOIS_SERVERS: Record = { // Generic TLDs com: 'whois.verisign-grs.com', net: 'whois.verisign-grs.com', org: 'whois.pir.org', info: 'whois.afilias.net', biz: 'whois.biz', // Popular new gTLDs io: 'whois.nic.io', co: 'whois.registry.co', me: 'whois.nic.me', tv: 'whois.nic.tv', cc: 'whois.nic.cc', app: 'whois.nic.google', dev: 'whois.nic.google', xyz: 'whois.nic.xyz', online: 'whois.nic.online', site: 'whois.nic.site', tech: 'whois.nic.tech', shop: 'whois.nic.shop', blog: 'whois.nic.blog', club: 'whois.nic.club', live: 'whois.nic.live', cloud: 'whois.nic.cloud', // Country Code TLDs kr: 'whois.kr', jp: 'whois.jprs.jp', cn: 'whois.cnnic.cn', uk: 'whois.nic.uk', de: 'whois.denic.de', fr: 'whois.nic.fr', it: 'whois.nic.it', es: 'whois.nic.es', nl: 'whois.domain-registry.nl', ru: 'whois.tcinet.ru', ca: 'whois.cira.ca', in: 'whois.registry.in', mx: 'whois.mx', cv: 'whois.nic.cv', au: 'whois.auda.org.au', br: 'whois.registro.br', // ccSLDs 'it.com': 'whois.centralnic.com', 'uk.com': 'whois.centralnic.com', 'us.com': 'whois.centralnic.com', 'eu.com': 'whois.centralnic.com', 'co.kr': 'whois.kr', 'or.kr': 'whois.kr', 'co.uk': 'whois.nic.uk', 'org.uk': 'whois.nic.uk', 'com.au': 'whois.auda.org.au', }; function getWhoisServer(domain: string): string { const parts = domain.toLowerCase().split('.'); // Check ccSLD first (e.g., co.kr, it.com) if (parts.length >= 3) { const ccSLD = parts.slice(-2).join('.'); if (WHOIS_SERVERS[ccSLD]) { return WHOIS_SERVERS[ccSLD]; } } // Then check TLD const tld = parts[parts.length - 1]; return WHOIS_SERVERS[tld] || 'whois.iana.org'; } function queryWhois(server: string, domain: string): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; // Special query formats for some servers let query = domain; if (server.includes('verisign')) { query = '=' + domain; } else if (server.includes('denic.de')) { query = '-T dn,ace ' + domain; } else if (server.includes('jprs.jp')) { query = domain + '/e'; } const socket = createConnection(43, server, () => { socket.write(query + '\r\n'); }); socket.setTimeout(15000); socket.on('data', (data) => { chunks.push(data); }); socket.on('end', () => { resolve(Buffer.concat(chunks).toString('utf-8')); }); socket.on('timeout', () => { socket.destroy(); reject(new Error('Connection timeout')); }); socket.on('error', (err) => { reject(err); }); }); } function extractReferral(raw: string): string | null { const patterns = [ /Registrar WHOIS Server:\s*(.+)/i, /Whois Server:\s*(.+)/i, /ReferralServer:\s*whois:\/\/(.+)/i, /refer:\s*(.+)/i, ]; for (const pattern of patterns) { const match = raw.match(pattern); if (match && match[1]) { const server = match[1].trim().replace('whois://', ''); if (server && !server.includes(' ')) { return server; } } } return null; } function checkAvailability(raw: string): boolean { const availablePatterns = [ 'no match for', 'not found', 'no data found', 'domain not found', 'no entries found', 'status: available', 'the queried object does not exist', 'is available for registration', 'this domain name has not been registered', ]; const rawLower = raw.toLowerCase(); return availablePatterns.some(p => rawLower.includes(p)); } export default async function handler(req: VercelRequest, res: VercelResponse) { // CORS res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'X-API-Key, Content-Type'); if (req.method === 'OPTIONS') { return res.status(200).end(); } // API Key check (optional) const apiKey = process.env.API_KEY; if (apiKey && req.headers['x-api-key'] !== apiKey) { return res.status(401).json({ error: 'Invalid API key' }); } const { domain } = req.query; if (!domain || typeof domain !== 'string') { return res.status(400).json({ error: 'Domain parameter required' }); } const domainName = domain.toLowerCase().trim(); const startTime = Date.now(); try { const whoisServer = getWhoisServer(domainName); let raw = await queryWhois(whoisServer, domainName); let usedServer = whoisServer; // Follow referral if exists const referral = extractReferral(raw); if (referral && referral !== whoisServer) { try { const referralRaw = await queryWhois(referral, domainName); if (referralRaw.length > raw.length / 2) { raw = referralRaw; usedServer = referral; } } catch { // Keep original response if referral fails } } const available = checkAvailability(raw); const queryTime = Date.now() - startTime; return res.status(200).json({ domain: domainName, available, whois_server: usedServer, raw, query_time_ms: queryTime, }); } catch (error) { const queryTime = Date.now() - startTime; return res.status(500).json({ domain: domainName, error: error instanceof Error ? error.message : 'Unknown error', query_time_ms: queryTime, }); } }