const HOST_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/; export const PARKED_DNS_PROVIDERS = ['arvan', 'cloudflare'] as const; export type ParkedDnsProvider = (typeof PARKED_DNS_PROVIDERS)[number]; export type ParkedAlias = { host: string; dns: ParkedDnsProvider; }; /** Strip protocol, path, trailing dot, and leading www. */ export function normalizeParkedHost(raw: string): string { let host = raw.trim().toLowerCase(); host = host.replace(/^https?:\/\//, ''); host = host.split('/')[0] ?? host; host = host.split(':')[0] ?? host; host = host.replace(/\.$/, ''); host = host.replace(/^www\./, ''); return host; } export function isValidParkedHost(host: string): boolean { return HOST_RE.test(host) && !host.endsWith('.local'); } export function isParkedDnsProvider(value: unknown): value is ParkedDnsProvider { return value === 'arvan' || value === 'cloudflare'; } export function parseParkedDnsMap(raw: unknown): Record { if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {}; const out: Record = {}; for (const [key, value] of Object.entries(raw as Record)) { const host = normalizeParkedHost(key); if (host && isParkedDnsProvider(value)) out[host] = value; } return out; } export function uniqueParkedAliases( raw: Array<{ host?: string; dns?: string } | string>, ): ParkedAlias[] { const seen = new Set(); const out: ParkedAlias[] = []; for (const item of raw) { const host = normalizeParkedHost(typeof item === 'string' ? item : (item.host ?? '')); if (!host || seen.has(host)) continue; seen.add(host); const dnsRaw = typeof item === 'string' ? 'arvan' : item.dns; out.push({ host, dns: isParkedDnsProvider(dnsRaw) ? dnsRaw : 'arvan', }); } return out; } export function uniqueParkedHosts(raw: string[]): string[] { return uniqueParkedAliases(raw).map((item) => item.host); }