import { getDomain } from "tldts"; /** * Extract eTLD+1 domain from a URL string. * Returns null for invalid URLs. */ export function extractDomain(url: string): string | null { try { const normalized = url.startsWith("http") ? url : "https://" + url; const parsed = getDomain(normalized); return parsed ?? null; } catch { return null; } } /** * Get displayable domain from a URL string. */ export function getDisplayDomain(url: string): string { try { const normalized = url.startsWith("http") ? url : "https://" + url; const { hostname } = new URL(normalized); return hostname; } catch { return url; } } /** * LV-2: true when the URL's hostname contains non-ASCII characters — a * potential homograph (Cyrillic "аpple.com" etc.) — OR a punycode (xn--) * label, which is the URL parser's normalized form of the same risk. * Matching/keying is NOT affected (tldts keeps Unicode labels distinct, * preventing cross-matching); this only drives a non-blocking UI warning so a * user who saved or imported such a URL can see it isn't the ASCII site. * Accepts bare hostnames as well as full URLs. */ export function detectHomograph(url: string): boolean { try { const hostname = new URL(url.includes("://") ? url : `https://${url}`).hostname; return ( // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — detects non-ASCII hostnames /[^\x00-\x7F]/.test(hostname) || hostname.split(".").some((l) => l.startsWith("xn--")) ); } catch { return false; } }