@devmehq/email-validator-js
Version:
Advanced email validation with MX records, SMTP verification, disposable email detection, batch processing, and caching. Production-ready with TypeScript support.
1,725 lines (1,715 loc) • 64.3 kB
JavaScript
'use strict';
var psl = require('psl');
var tinyLru = require('tiny-lru');
var node_dns = require('node:dns');
var stringSimilarityJs = require('string-similarity-js');
var net = require('node:net');
const mxCache = tinyLru.lru(500, 36e5);
const disposableCache = tinyLru.lru(1e3, 864e5);
const freeCache = tinyLru.lru(1e3, 864e5);
const domainValidCache = tinyLru.lru(1e3, 864e5);
const smtpCache = tinyLru.lru(500, 18e5);
const domainSuggestionCache = tinyLru.lru(1e3, 864e5);
const whoisCache = tinyLru.lru(200, 36e5);
function clearAllCaches() {
mxCache.clear();
disposableCache.clear();
freeCache.clear();
domainValidCache.clear();
smtpCache.clear();
domainSuggestionCache.clear();
whoisCache.clear();
}
async function resolveMxRecords(domain) {
const cached = mxCache.get(domain);
if (cached !== void 0) {
return cached;
}
try {
const records = await node_dns.promises.resolveMx(domain);
records.sort((a, b) => {
if (a.priority < b.priority) {
return -1;
}
if (a.priority > b.priority) {
return 1;
}
return 0;
});
const exchanges = records.map((record) => record.exchange);
mxCache.set(domain, exchanges);
return exchanges;
} catch (error) {
mxCache.set(domain, []);
throw error;
}
}
const COMMON_EMAIL_DOMAINS = [
// Popular free email providers
"gmail.com",
"yahoo.com",
"outlook.com",
"hotmail.com",
"icloud.com",
"aol.com",
"mail.com",
"protonmail.com",
"proton.me",
"yandex.com",
// Business/Professional email providers
"google.com",
"microsoft.com",
"apple.com",
"amazon.com",
"amazonaws.com",
// Secure/Privacy-focused providers
"tutanota.com",
"tuta.com",
"fastmail.com",
"posteo.de",
"mailbox.org",
"runbox.com",
"startmail.com",
// European providers
"gmx.com",
"gmx.de",
"gmx.net",
"web.de",
"freenet.de",
"t-online.de",
"arcor.de",
// Business email hosting
"zoho.com",
"titan.email",
"neo.space",
"rackspace.com",
"ionos.com",
"hostinger.com",
"bluehost.com",
"siteground.com",
"dreamhost.com",
"inmotionhosting.com",
"godaddy.com",
"namecheap.com",
"migadu.com",
"purelymail.com",
"infomaniak.com",
"uberspace.de",
"manitu.de",
"hetzner.com",
"scalahosting.com",
"hostpapa.com",
"liquidweb.com",
"icewarp.com",
"hostgator.com",
"a2hosting.com",
"spacemail.com",
"mxroute.com",
// Regional providers
"qq.com",
"163.com",
"126.com",
"sina.com",
"naver.com",
"daum.net",
"kakao.com"
];
const TYPO_PATTERNS = {
"gmail.com": ["gmai.com", "gmial.com", "gmali.com", "gmil.com", "gmaill.com", "gmail.co", "gmail.cm"],
"yahoo.com": ["yaho.com", "yahooo.com", "yahoo.co", "yahoo.cm", "yhaoo.com"],
"hotmail.com": ["hotmai.com", "hotmial.com", "hotmal.com", "hotmil.com", "hotmail.co", "hotmail.cm"],
"outlook.com": ["outlok.com", "outloo.com", "outlook.co", "outlook.cm", "outlokk.com"]
};
function getSimilarityThreshold(domain) {
const length = domain.length;
if (length <= 6)
return 0.85;
if (length <= 10)
return 0.8;
return 0.75;
}
function defaultDomainSuggestionMethod(domain, commonDomains) {
if (!domain || domain.length < 3) {
return null;
}
const domainsToCheck = commonDomains || COMMON_EMAIL_DOMAINS;
const lowerDomain = domain.toLowerCase();
const cacheKey = `${lowerDomain}:${domainsToCheck.length}`;
const cached = domainSuggestionCache.get(cacheKey);
if (cached !== void 0) {
return cached ? { original: domain, ...cached } : null;
}
if (domainsToCheck.includes(lowerDomain)) {
domainSuggestionCache.set(cacheKey, null);
return null;
}
for (const [correctDomain, typos] of Object.entries(TYPO_PATTERNS)) {
if (typos.includes(lowerDomain)) {
const result = {
original: domain,
suggested: correctDomain,
confidence: 0.95
// High confidence for known typo patterns
};
domainSuggestionCache.set(cacheKey, { suggested: result.suggested, confidence: result.confidence });
return result;
}
}
let bestMatch = null;
const threshold = getSimilarityThreshold(lowerDomain);
for (const commonDomain of domainsToCheck) {
const similarity = stringSimilarityJs.stringSimilarity(lowerDomain, commonDomain.toLowerCase());
if (similarity >= threshold) {
if (!bestMatch || similarity > bestMatch.similarity) {
bestMatch = { domain: commonDomain, similarity };
}
}
}
if (!bestMatch) {
for (const commonDomain of domainsToCheck) {
if (Math.abs(lowerDomain.length - commonDomain.length) <= 2) {
const similarity = stringSimilarityJs.stringSimilarity(lowerDomain, commonDomain.toLowerCase());
if (similarity >= 0.7) {
if (!bestMatch || similarity > bestMatch.similarity) {
bestMatch = { domain: commonDomain, similarity };
}
}
}
}
}
if (bestMatch) {
if (bestMatch.domain.charAt(0) !== lowerDomain.charAt(0) && bestMatch.similarity < 0.9) {
domainSuggestionCache.set(cacheKey, null);
return null;
}
const result = {
original: domain,
suggested: bestMatch.domain,
confidence: bestMatch.similarity
};
domainSuggestionCache.set(cacheKey, { suggested: result.suggested, confidence: result.confidence });
return result;
}
domainSuggestionCache.set(cacheKey, null);
return null;
}
function suggestDomain(params) {
const { domain, customMethod, commonDomains } = params;
if (!domain || domain.length < 3) {
return null;
}
if (customMethod) {
try {
return customMethod(domain);
} catch (error) {
console.warn("Custom domain suggestion method failed, falling back to default:", error);
}
}
return defaultDomainSuggestionMethod(domain, commonDomains);
}
function suggestEmailDomain(email, commonDomains) {
if (!email || !email.includes("@")) {
return null;
}
const [localPart, domain] = email.split("@");
if (!domain || !localPart) {
return null;
}
const suggestion = suggestDomain({ domain, commonDomains });
if (suggestion) {
return {
original: email,
suggested: `${localPart}@${suggestion.suggested}`,
confidence: suggestion.confidence
};
}
return null;
}
function isCommonDomain(domain, commonDomains) {
const domainsToCheck = commonDomains || COMMON_EMAIL_DOMAINS;
return domainsToCheck.includes(domain.toLowerCase());
}
function getDomainSimilarity(domain1, domain2) {
return stringSimilarityJs.stringSimilarity(domain1.toLowerCase(), domain2.toLowerCase());
}
const NAME_SEPARATORS = [".", "_", "-"];
const COMMON_NAME_SUFFIXES = [
"mail",
"email",
"contact",
"info",
"admin",
"support",
"sales",
"help",
"noreply",
"no-reply",
"donotreply",
"notifications",
"alerts"
];
const CONTEXTUAL_SUFFIXES = [
"dev",
"company",
"team",
"group",
"office",
"work",
"personal",
"home",
"private",
"business",
"corp",
"inc",
"ltd",
"llc",
"org"
];
const COMMON_TITLES = [
"mr",
"mrs",
"ms",
"miss",
"dr",
"prof",
"sir",
"lord",
"lady",
"captain",
"rev",
"father",
"sister"
];
const COMMON_FIRST_NAMES = /* @__PURE__ */ new Set([
// English names
"james",
"john",
"robert",
"michael",
"william",
"david",
"richard",
"joseph",
"thomas",
"charles",
"christopher",
"daniel",
"matthew",
"kenneth",
"anthony",
"mark",
"donald",
"steven",
"brian",
"paul",
"andrew",
"joshua",
"kevin",
"eric",
"stephen",
"benjamin",
"samuel",
"gregory",
"frank",
"alexander",
"mary",
"patricia",
"jennifer",
"linda",
"elizabeth",
"barbara",
"susan",
"jessica",
"sarah",
"margaret",
"karen",
"lisa",
"nancy",
"betty",
"helen",
"sandra",
"donna",
"carol",
"ruth",
"sharon",
"michelle",
"laura",
"kimberly",
"deborah",
"amy",
"angela",
"ashley",
"brenda",
"catherine",
"emma",
"olivia",
"sophia",
"ava",
"isabella",
"mia",
"charlotte",
"amelia",
"harper",
"evelyn",
"abigail",
// Spanish/Latin names
"juan",
"carlos",
"jose",
"luis",
"antonio",
"francisco",
"manuel",
"miguel",
"pedro",
"jorge",
"maria",
"carmen",
"isabel",
"ana",
"lucia",
"sofia",
"valentina",
"camila",
"gabriela",
"daniela",
// Arabic names
"mohammed",
"mohamed",
"ahmed",
"ali",
"hassan",
"ibrahim",
"omar",
"khalid",
"abdullah",
"yusuf",
"fatima",
"aisha",
"zainab",
"maryam",
"khadija",
"sara",
"layla",
"noor",
"hana",
"amira",
// Asian names
"wei",
"jing",
"chen",
"li",
"wang",
"zhang",
"liu",
"yang",
"huang",
"zhao",
"satoshi",
"takeshi",
"hiroshi",
"yuki",
"akira",
"kenji",
"taro",
"naoko",
"yuko",
"keiko",
"raj",
"arjun",
"rohit",
"amit",
"vikram",
"priya",
"anjali",
"neha",
"pooja",
"divya",
// European names
"jean",
"pierre",
"jacques",
"marie",
"francois",
"giuseppe",
"marco",
"alessandro",
"francesca",
"giulia",
"hans",
"klaus",
"peter",
"anna",
"katrin",
"vladimir",
"sergey",
"olga",
"natasha",
"ivan",
// African names
"kwame",
"kofi",
"amara",
"fatou",
"aminata",
"ousmane",
"amadou",
"ibrahim",
"sekou",
"mariama"
]);
const COMMON_LAST_NAMES = /* @__PURE__ */ new Set([
// English surnames
"smith",
"johnson",
"williams",
"brown",
"jones",
"garcia",
"miller",
"davis",
"rodriguez",
"martinez",
"hernandez",
"lopez",
"gonzalez",
"wilson",
"anderson",
"thomas",
"taylor",
"moore",
"jackson",
"martin",
"lee",
"thompson",
"white",
"harris",
"clark",
"lewis",
"robinson",
"walker",
"young",
"king",
// Spanish surnames
"sanchez",
"ramirez",
"torres",
"flores",
"rivera",
"gomez",
"diaz",
"reyes",
"morales",
"cruz",
"ortiz",
"gutierrez",
"chavez",
"ruiz",
"alvarez",
"castillo",
"jimenez",
"vasquez",
"romero",
"herrera",
// Asian surnames
"wang",
"li",
"zhang",
"chen",
"liu",
"yang",
"huang",
"zhao",
"wu",
"zhou",
"tanaka",
"watanabe",
"ito",
"yamamoto",
"nakamura",
"kim",
"park",
"choi",
"jung",
"kang",
"patel",
"sharma",
"kumar",
"singh",
"gupta",
"khan",
"reddy",
"rao",
"mehta",
"shah",
// Other common surnames
"nguyen",
"tran",
"pham",
"murphy",
"kelly",
"sullivan",
"walsh",
"connor",
"ryan",
"byrne",
"schmidt",
"mueller",
"schneider",
"fischer",
"weber",
"rossi",
"russo",
"ferrari",
"bianchi",
"romano",
"silva",
"santos",
"ferreira",
"pereira",
"costa",
"ivanov",
"petrov",
"sidorov",
"smirnov",
"volkov"
]);
function isYearLike(str) {
return /^(19|20)\d{2}$/.test(str);
}
function isKnownFirstName(str) {
return COMMON_FIRST_NAMES.has(str.toLowerCase());
}
function isKnownLastName(str) {
return COMMON_LAST_NAMES.has(str.toLowerCase());
}
function isTitle(str) {
return COMMON_TITLES.includes(str.toLowerCase().replace(".", ""));
}
function getFirstNameScore(str) {
const lower = str.toLowerCase();
if (COMMON_FIRST_NAMES.has(lower))
return 1;
if (COMMON_LAST_NAMES.has(lower))
return 0.3;
if (str.length >= 2 && str.length <= 15 && /^[a-zA-Z]+$/.test(str))
return 0.5;
return 0;
}
function getLastNameScore(str) {
const lower = str.toLowerCase();
if (COMMON_LAST_NAMES.has(lower))
return 1;
if (COMMON_FIRST_NAMES.has(lower))
return 0.3;
if (str.length >= 2 && str.length <= 20 && /^[a-zA-Z]+$/.test(str))
return 0.5;
return 0;
}
function capitalizeName(str) {
if (!str)
return "";
const specialPatterns = [
{
pattern: /^(o')([a-z]+)/i,
format: (m) => `O'${m[2].charAt(0).toUpperCase()}${m[2].slice(1).toLowerCase()}`
},
{
pattern: /^(mac)([a-z]+)/i,
format: (m) => `Mac${m[2].charAt(0).toUpperCase()}${m[2].slice(1).toLowerCase()}`
},
{
pattern: /^(mc)([a-z]+)/i,
format: (m) => `Mc${m[2].charAt(0).toUpperCase()}${m[2].slice(1).toLowerCase()}`
},
{
pattern: /^(van|von|de|del|dela|da|di|le|la|du|den|der|ter|ten)(\s+)([a-z]+)/i,
format: (m) => m[1].toLowerCase() + m[2] + m[3].charAt(0).toUpperCase() + m[3].slice(1).toLowerCase()
}
];
for (const { pattern, format } of specialPatterns) {
const match = str.match(pattern);
if (match) {
return format(match);
}
}
if (str.includes("-")) {
return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("-");
}
if (str.includes("'") && str.indexOf("'") > 0) {
const parts = str.split("'");
return parts.map((part, i) => i === 0 ? part.charAt(0).toUpperCase() + part.slice(1).toLowerCase() : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("'");
}
if (/^[a-zA-Z]/.test(str)) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
return str;
}
function parseCompositeNamePart(part) {
const hasNumbers = /\d/.test(part);
let confidence = 1;
let cleaned = part;
const leetSpeakMap = {
"0": "o",
"1": "i",
"3": "e",
"4": "a",
"5": "s",
"7": "t",
"8": "b"
};
if (hasNumbers && part.length >= 3) {
let leetCleaned = part.toLowerCase();
for (const [num, letter] of Object.entries(leetSpeakMap)) {
leetCleaned = leetCleaned.replace(new RegExp(num, "g"), letter);
}
if (isKnownFirstName(leetCleaned) || isKnownLastName(leetCleaned)) {
cleaned = leetCleaned;
confidence = 0.7;
}
}
if (cleaned === part) {
const withoutYear = part.replace(/(19|20)\d{2}$/, "");
if (withoutYear !== part && withoutYear.length >= 2) {
cleaned = withoutYear;
confidence = 0.8;
} else {
const pureAlpha = part.replace(/\d+/g, "");
if (pureAlpha.length >= 2) {
cleaned = pureAlpha;
confidence = hasNumbers ? 0.85 : 1;
} else if (pureAlpha.length === 1 && /^[a-zA-Z]$/.test(pureAlpha)) {
cleaned = pureAlpha;
confidence = 0.6;
} else {
const baseMatch2 = part.match(/^([a-zA-Z]+)\d*$/);
if (baseMatch2) {
cleaned = baseMatch2[1];
confidence = 0.75;
} else {
cleaned = part;
confidence = 0.4;
}
}
}
}
if (cleaned !== part) {
if (isKnownFirstName(cleaned) || isKnownLastName(cleaned)) {
confidence = Math.min(1, confidence + 0.2);
}
}
const baseMatch = part.match(/^([a-zA-Z]+[a-zA-Z0-9]*?)\d*$/);
const base = baseMatch ? baseMatch[1] : part;
return { base, hasNumbers, cleaned, confidence };
}
function isLikelyName(str, allowNumbers = false, allowSingleLetter = false) {
if (!str)
return false;
if (str.length < 2 && !allowSingleLetter)
return false;
if (str.length === 1 && allowSingleLetter && /^[a-zA-Z]$/.test(str))
return true;
if (COMMON_NAME_SUFFIXES.includes(str.toLowerCase()))
return false;
if (allowNumbers) {
if (/^\d+$/.test(str))
return false;
return true;
}
const digitCount = (str.match(/\d/g) || []).length;
if (digitCount > str.length * 0.3)
return false;
return true;
}
function defaultNameDetectionMethod(email) {
if (!email || !email.includes("@")) {
return null;
}
const [localPart] = email.split("@");
if (!localPart) {
return null;
}
const localWithoutAlias = localPart.split("+")[0];
const cleanedLocal = localWithoutAlias;
if (cleanedLocal.toLowerCase() === "no-reply" || cleanedLocal.toLowerCase() === "noreply") {
return null;
}
let firstName;
let lastName;
let confidence = 0;
const camelCasePatterns = [
/^([a-z]+)([A-Z][a-z]+)$/,
// johnDoe
/^([A-Z][a-z]+)([A-Z][a-z]+)$/,
// JohnSmith
/^([a-z]+)([A-Z][a-z]+)([A-Z][a-z]+)$/
// johnMcDonald
];
for (const pattern of camelCasePatterns) {
const match = cleanedLocal.match(pattern);
if (match) {
if (match.length === 3) {
const [, first, last] = match;
if (isLikelyName(first) && isLikelyName(last)) {
const firstScore = getFirstNameScore(first);
const lastScore = getLastNameScore(last);
firstName = capitalizeName(first);
lastName = capitalizeName(last);
confidence = 0.7 + (firstScore + lastScore) * 0.15;
break;
}
} else if (match.length === 4) {
const [, first, middle, last] = match;
const combined = middle + last;
if (isLikelyName(first)) {
firstName = capitalizeName(first);
lastName = capitalizeName(combined);
confidence = 0.75;
break;
}
}
}
}
if (!firstName && !lastName) {
for (const separator of NAME_SEPARATORS) {
if (cleanedLocal.includes(separator)) {
const parts = cleanedLocal.split(separator).filter((p) => p.length > 0);
if (parts.length === 2) {
const [first, last] = parts;
if (isTitle(first)) {
continue;
}
const firstParsed = parseCompositeNamePart(first);
const lastParsed = parseCompositeNamePart(last);
const firstNameScore = getFirstNameScore(firstParsed.cleaned);
const lastNameScore = getLastNameScore(lastParsed.cleaned);
const reverseScore = getLastNameScore(firstParsed.cleaned) + getFirstNameScore(lastParsed.cleaned);
const normalScore = firstNameScore + lastNameScore;
const bothPartsValid = isLikelyName(first, true) && isLikelyName(last, true);
if (bothPartsValid) {
const useReversed = reverseScore > normalScore * 1.2;
if (firstParsed.hasNumbers || lastParsed.hasNumbers) {
const cleanedFirst = firstParsed.cleaned;
const cleanedLast = lastParsed.cleaned;
if (isLikelyName(cleanedFirst, false, true) && isLikelyName(cleanedLast, false, true)) {
if (useReversed) {
firstName = capitalizeName(cleanedLast);
lastName = capitalizeName(cleanedFirst);
} else {
firstName = capitalizeName(cleanedFirst);
lastName = capitalizeName(cleanedLast);
}
const baseConfidence = 0.7;
const separatorBonus = separator === "." ? 0.15 : separator === "_" ? 0.05 : 0;
const nameRecognitionBonus = normalScore > 0.5 ? 0.1 : 0;
const cleaningPenalty = (firstParsed.confidence + lastParsed.confidence) / 2 - 1;
confidence = Math.min(0.95, baseConfidence + separatorBonus + nameRecognitionBonus + cleaningPenalty);
} else {
firstName = capitalizeName(first);
lastName = capitalizeName(last);
confidence = 0.5;
}
} else {
if (useReversed) {
firstName = capitalizeName(last);
lastName = capitalizeName(first);
} else {
firstName = capitalizeName(first);
lastName = capitalizeName(last);
}
const baseConfidence = 0.75;
const separatorBonus = separator === "." ? 0.15 : separator === "_" ? 0.05 : 0;
const nameRecognitionBonus = Math.min(0.15, normalScore * 0.15);
confidence = Math.min(0.95, baseConfidence + separatorBonus + nameRecognitionBonus);
}
break;
}
} else if (parts.length === 3) {
const [first, middle, last] = parts;
const firstParsed = parseCompositeNamePart(first);
const middleParsed = parseCompositeNamePart(middle);
const lastParsed = parseCompositeNamePart(last);
const isLastSuffix = COMMON_NAME_SUFFIXES.includes(last.toLowerCase()) || CONTEXTUAL_SUFFIXES.includes(last.toLowerCase()) || isYearLike(last);
if (isLastSuffix) {
if (isLikelyName(first, true) && isLikelyName(middle, true)) {
const cleanedFirst = firstParsed.hasNumbers ? firstParsed.cleaned : first;
const cleanedMiddle = middleParsed.hasNumbers ? middleParsed.cleaned : middle;
if (isLikelyName(cleanedFirst, false, true) && isLikelyName(cleanedMiddle, false, true)) {
firstName = capitalizeName(cleanedFirst);
lastName = capitalizeName(cleanedMiddle);
confidence = 0.7;
} else {
firstName = capitalizeName(first);
lastName = capitalizeName(middle);
confidence = 0.65;
}
break;
}
} else if (isLikelyName(first, true) && isLikelyName(last, true)) {
const cleanedFirst = firstParsed.hasNumbers ? firstParsed.cleaned : first;
const cleanedLast = lastParsed.hasNumbers ? lastParsed.cleaned : last;
if (isLikelyName(cleanedFirst, false, true) && isLikelyName(cleanedLast, false, true)) {
firstName = capitalizeName(cleanedFirst);
lastName = capitalizeName(cleanedLast);
confidence = 0.75;
} else {
firstName = capitalizeName(first);
lastName = capitalizeName(last);
confidence = 0.55;
}
break;
}
} else if (parts.length > 3) {
const firstPart = parts[0];
const lastPartLower = parts[parts.length - 1].toLowerCase();
const isLastPartSuffix = COMMON_NAME_SUFFIXES.includes(lastPartLower) || CONTEXTUAL_SUFFIXES.includes(lastPartLower) || isYearLike(parts[parts.length - 1]);
const effectiveLastIndex = isLastPartSuffix ? parts.length - 2 : parts.length - 1;
const lastToUse = effectiveLastIndex >= 0 ? parts[effectiveLastIndex] : null;
if (lastToUse && isLikelyName(firstPart, true) && isLikelyName(lastToUse, true)) {
const firstParsed = parseCompositeNamePart(firstPart);
const lastParsed = parseCompositeNamePart(lastToUse);
const cleanedFirst = firstParsed.hasNumbers ? firstParsed.cleaned : firstPart;
const cleanedLast = lastParsed.hasNumbers ? lastParsed.cleaned : lastToUse;
if (isLikelyName(cleanedFirst, false, true) && isLikelyName(cleanedLast, false, true)) {
firstName = capitalizeName(cleanedFirst);
lastName = capitalizeName(cleanedLast);
confidence = 0.6;
} else {
firstName = capitalizeName(firstPart);
lastName = capitalizeName(lastToUse);
confidence = 0.5;
}
break;
}
}
}
}
}
if (!firstName && !lastName) {
const underscoreMatch = cleanedLocal.match(/^([A-Z_]+)$/);
if (underscoreMatch) {
const parts = cleanedLocal.toLowerCase().split("_").filter((p) => p.length > 0);
if (parts.length === 2) {
const [first, last] = parts;
if (isLikelyName(first) && isLikelyName(last)) {
firstName = capitalizeName(first);
lastName = capitalizeName(last);
confidence = 0.65;
}
}
}
}
if (!firstName && !lastName) {
const parsed = parseCompositeNamePart(cleanedLocal);
if (isLikelyName(cleanedLocal, true)) {
if (/^[a-zA-Z]+$/.test(cleanedLocal)) {
const nameScore = Math.max(getFirstNameScore(cleanedLocal), getLastNameScore(cleanedLocal));
if (getFirstNameScore(cleanedLocal) >= getLastNameScore(cleanedLocal)) {
firstName = capitalizeName(cleanedLocal);
} else {
lastName = capitalizeName(cleanedLocal);
}
confidence = 0.4 + nameScore * 0.3;
} else if (parsed.hasNumbers && parsed.cleaned.length >= 2) {
const cleanedScore = Math.max(getFirstNameScore(parsed.cleaned), getLastNameScore(parsed.cleaned));
if (getFirstNameScore(parsed.cleaned) >= getLastNameScore(parsed.cleaned)) {
firstName = capitalizeName(parsed.cleaned);
} else {
lastName = capitalizeName(parsed.cleaned);
}
confidence = 0.3 + cleanedScore * 0.2 + parsed.confidence * 0.2;
}
}
}
if (!firstName && !lastName) {
return null;
}
return {
firstName,
lastName,
confidence: Math.max(0, Math.min(1, confidence))
// Ensure confidence is between 0 and 1
};
}
function detectNameFromEmail(params) {
const { email, customMethod } = params;
if (!email || !email.includes("@")) {
return null;
}
if (customMethod) {
try {
return customMethod(email);
} catch (error) {
console.warn("Custom name detection method failed, falling back to default:", error);
}
}
return defaultNameDetectionMethod(email);
}
function detectName(email) {
return detectNameFromEmail({ email });
}
function isOverQuota(smtpReply) {
return Boolean(smtpReply && /(over quota)/gi.test(smtpReply));
}
function isInvalidMailboxError(smtpReply) {
return Boolean(smtpReply && /^(510|511|513|550|551|553)/.test(smtpReply) && !/(junk|spam|openspf|spoofing|host|rbl.+blocked)/gi.test(smtpReply));
}
function isMultilineGreet(smtpReply) {
return Boolean(smtpReply && /^(250|220)-/.test(smtpReply));
}
async function verifyMailboxSMTP(params) {
const { local, domain, mxRecords = [], timeout, debug, port = 25, retryAttempts = 1 } = params;
const log = debug ? console.debug : (..._args) => {
};
if (!mxRecords || mxRecords.length === 0) {
return false;
}
for (let mxIndex = 0; mxIndex < Math.min(mxRecords.length, 3); mxIndex++) {
const mxRecord = mxRecords[mxIndex];
for (let attempt = 0; attempt < retryAttempts; attempt++) {
const result = await attemptVerification({
mxRecord,
local,
domain,
port,
timeout,
log,
attempt
});
if (result !== null) {
return result;
}
if (attempt < retryAttempts - 1) {
await new Promise((resolve) => setTimeout(resolve, Math.min(1e3 * (attempt + 1), 3e3)));
}
}
}
return null;
}
async function attemptVerification(params) {
const { mxRecord, local, domain, port, timeout, log, attempt } = params;
return new Promise((resolve) => {
log(`[verifyMailboxSMTP] connecting to ${mxRecord}:${port} (attempt ${attempt + 1})`);
const socket = net.connect({
host: mxRecord,
port
});
let resTimeout = null;
let resolved = false;
let cleaned = false;
const cleanup = () => {
if (cleaned)
return;
cleaned = true;
if (resTimeout) {
clearTimeout(resTimeout);
resTimeout = null;
}
if (socket && !socket.destroyed) {
socket.removeAllListeners();
socket.destroy();
}
};
const ret = (result) => {
if (resolved)
return;
resolved = true;
if (!(socket === null || socket === void 0 ? void 0 : socket.destroyed)) {
log("[verifyMailboxSMTP] closing socket");
socket === null || socket === void 0 ? void 0 : socket.write("QUIT\r\n");
socket === null || socket === void 0 ? void 0 : socket.end();
}
cleanup();
resolve(result);
};
const messages = [`HELO ${domain}`, `MAIL FROM: <${local}@${domain}>`, `RCPT TO: <${local}@${domain}>`];
log("[verifyMailboxSMTP] writing messages", messages);
socket.on("data", (data) => {
const dataString = String(data);
log("[verifyMailboxSMTP] got data", dataString);
if (isInvalidMailboxError(dataString))
return ret(false);
if (isOverQuota(dataString))
return ret(false);
if (!dataString.includes("220") && !dataString.includes("250"))
return ret(null);
if (isMultilineGreet(dataString))
return;
if (messages.length > 0) {
const message = messages.shift();
log("[verifyMailboxSMTP] writing message", message);
return socket.write(`${message}\r
`);
}
ret(true);
});
socket.on("error", (err) => {
log("[verifyMailboxSMTP] error in socket", err);
ret(null);
});
socket.on("close", (err) => {
if (!resolved) {
log("[verifyMailboxSMTP] close socket", err);
ret(null);
}
});
socket.on("timeout", () => {
log("[verifyMailboxSMTP] timeout socket");
ret(null);
});
resTimeout = setTimeout(() => {
log(`[verifyMailboxSMTP] timed out (${timeout} ms)`);
if (!resolved) {
socket.destroy();
ret(null);
}
}, timeout);
});
}
exports.VerificationErrorCode = void 0;
(function(VerificationErrorCode2) {
VerificationErrorCode2["INVALID_FORMAT"] = "INVALID_FORMAT";
VerificationErrorCode2["INVALID_DOMAIN"] = "INVALID_DOMAIN";
VerificationErrorCode2["NO_MX_RECORDS"] = "NO_MX_RECORDS";
VerificationErrorCode2["SMTP_CONNECTION_FAILED"] = "SMTP_CONNECTION_FAILED";
VerificationErrorCode2["SMTP_TIMEOUT"] = "SMTP_TIMEOUT";
VerificationErrorCode2["MAILBOX_NOT_FOUND"] = "MAILBOX_NOT_FOUND";
VerificationErrorCode2["MAILBOX_FULL"] = "MAILBOX_FULL";
VerificationErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
VerificationErrorCode2["DISPOSABLE_EMAIL"] = "DISPOSABLE_EMAIL";
VerificationErrorCode2["FREE_EMAIL_PROVIDER"] = "FREE_EMAIL_PROVIDER";
})(exports.VerificationErrorCode || (exports.VerificationErrorCode = {}));
function isValidEmailDomain(emailOrDomain) {
let [_, emailDomain] = (emailOrDomain === null || emailOrDomain === void 0 ? void 0 : emailOrDomain.split("@")) || [];
if (!emailDomain) {
emailDomain = _;
}
if (!emailDomain) {
return false;
}
const cached = domainValidCache.get(emailDomain);
if (cached !== void 0) {
return cached;
}
try {
const result = psl.isValid(emailDomain) || false;
domainValidCache.set(emailDomain, result);
return result;
} catch (_e) {
domainValidCache.set(emailDomain, false);
return false;
}
}
function isValidEmail(emailAddress) {
if (!emailAddress || typeof emailAddress !== "string") {
return false;
}
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const emailLower = emailAddress.toLowerCase();
if (emailLower.indexOf(".+") !== -1)
return false;
if (emailLower.indexOf("..") !== -1)
return false;
if (emailLower.startsWith(".") || emailLower.endsWith("."))
return false;
const parts = emailAddress.split("@");
if (parts.length !== 2)
return false;
const [localPart, domain] = parts;
if (!localPart || !domain)
return false;
if (localPart.length > 64)
return false;
if (domain.length > 253)
return false;
return re.test(emailLower);
}
const defaultRegex = {
domainName: "Domain Name: *([^\\s]+)",
registrar: "Registrar: *(.+)",
updatedDate: "Updated Date: *(.+)",
creationDate: "Creat(ed|ion) Date: *(.+)",
expirationDate: "Expir\\w+ Date: *(.+)",
status: "Status:\\s*(.+)\\s*\\n",
dateFormat: "yyyy-MM-dd'T'HH:mm:ss'Z'",
notFound: "(No match for |Domain not found|NOT FOUND\\s)"
};
const comRegex = { ...defaultRegex, notFound: "No match for " };
const orgRegex = { ...defaultRegex, notFound: "^(NOT FOUND|Domain not found)" };
const auRegex = {
...defaultRegex,
updatedDate: "Last Modified: *(.+)",
registrar: "Registrar Name: *(.+)",
rateLimited: "WHOIS LIMIT EXCEEDED",
notFound: "^NOT FOUND"
};
const usRegex = {
...defaultRegex,
status: "Domain Status: *(.+)",
expirationDate: "Registrar Registration Expiration Date: *(.+)",
notFound: "^No Data Found"
};
const ruRegex = {
...defaultRegex,
domainName: "domain: *([^\\s]+)",
registrar: "registrar: *(.+)",
expirationDate: "paid-till: *(.+)",
status: "state: *(.+)",
notFound: "No entries found"
};
const ukRegex = {
...defaultRegex,
domainName: "Domain name:\\s*([^\\s]+)",
dateFormat: "dd-MMM-yyyy"
};
const frRegex = {
...defaultRegex,
domainName: "domain: *([^\\s]+)",
expirationDate: "Expir\\w+ Date:\\s?(.+)",
updatedDate: "last-update: *(.+)",
notFound: "(No entries found in |%% NOT FOUND)"
};
const nlRegex = {
...defaultRegex,
notFound: "\\.nl is free",
rateLimited: "maximum number of requests per second exceeded"
};
const fiRegex = {
...defaultRegex,
domainName: "domain\\.*: *([\\S]+)",
registrar: "registrar\\.*: *(.*)",
status: "status\\.*: *([\\S]+)",
dateFormat: "dd.MM.yyyy HH:mm:ss"
};
const jpRegex = {
...defaultRegex,
domainName: "\\[Domain Name\\]\\s*([^\\s]+)",
dateFormat: "yyyy/MM/dd",
notFound: "No match!!"
};
const plRegex = {
...defaultRegex,
domainName: "DOMAIN NAME: *([^\\s]+)\\s+",
status: "Registration status:\\n\\s*(.+)",
expirationDate: "renewal date: *(.+)",
dateFormat: "yyyy.MM.dd HH:mm:ss"
};
const brRegex = {
...defaultRegex,
domainName: "domain: *([^\\s]+)\\n",
dateFormat: "yyyyMMdd"
};
const euRegex = {
...defaultRegex,
domainName: "Domain: *([^\\n\\r]+)",
registrar: "Registrar: *\\n *Name: *([^\\n\\r]+)",
notFound: "Status: AVAILABLE"
};
const eeRegex = {
...defaultRegex,
domainName: "Domain: *[\\n\\r]+\\s*name: *([^\\n\\r]+)",
status: "Domain: *[\\n\\r]+\\s*name: *[^\\n\\r]+\\s*status: *([^\\n\\r]+)"
};
const krRegex = {
...defaultRegex,
domainName: "Domain Name\\s*: *([^\\s]+)",
dateFormat: "yyyy. MM. dd.",
notFound: "The requested domain was not found "
};
const bgRegex = {
...defaultRegex,
domainName: "DOMAIN NAME: *([^\\s]+)",
status: "registration status:\\s*(.+)",
notFound: "registration status: available",
rateLimited: "Query limit exceeded"
};
const deRegex = {
...defaultRegex,
domainName: "Domain: *([^\\s]+)",
notFound: "Status: *free"
};
const atRegex = {
...defaultRegex,
domainName: "domain: *([^\\s]+)",
notFound: " nothing found",
dateFormat: "yyyyMMdd HH:mm:ss",
rateLimited: "Quota exceeded"
};
const caRegex = {
...defaultRegex,
domainName: "Domain Name: *([^\\s]+)",
notFound: "Not found: "
};
const beRegex = {
...defaultRegex,
domainName: "Domain:\\s*([^\\s]+)",
dateFormat: "ddd MMM dd yyyy",
notFound: "Status:\\s*AVAILABLE"
};
const infoRegex = {
...defaultRegex,
notFound: "^(NOT FOUND|Domain not found)"
};
const kgRegex = {
...defaultRegex,
domainName: "^Domain\\s*([^\\s]+)",
dateFormat: "ddd MMM dd HH:mm:ss yyyy",
notFound: "domain is available for registration"
};
const idRegex = {
...defaultRegex,
domainName: "Domain Name:([^\\s]+)",
dateFormat: "dd-MMM-yyyy HH:mm:ss 'UTC'",
notFound: "DOMAIN NOT FOUND"
};
const skRegex = {
...defaultRegex,
domainName: "Domain:\\s*([^\\s]+)",
notFound: "Domain not found"
};
const seRegex = {
...defaultRegex,
domainName: "domain\\.*: *([^\\s]+)",
notFound: '\\" not found.'
};
const isRegex = {
...defaultRegex,
domainName: "domain\\.*: *([^\\s]+)",
dateFormat: "MMM dd yyyy",
notFound: "No entries found for query"
};
const coRegex = {
...defaultRegex,
notFound: "No Data Found"
};
function parseDate(dateStr, format) {
try {
dateStr = dateStr.trim();
if (format === "yyyy-MM-dd'T'HH:mm:ss'Z'" || dateStr.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
return new Date(dateStr);
}
let date = null;
switch (format) {
case "yyyy/MM/dd": {
const parts = dateStr.split("/");
if (parts.length === 3) {
date = /* @__PURE__ */ new Date(`${parts[0]}-${parts[1].padStart(2, "0")}-${parts[2].padStart(2, "0")}`);
}
break;
}
case "dd-MMM-yyyy": {
date = new Date(dateStr.replace(/-/g, " "));
break;
}
case "dd.MM.yyyy HH:mm:ss": {
const [datePart, timePart] = dateStr.split(" ");
if (datePart) {
const parts = datePart.split(".");
if (parts.length === 3) {
const dateString = `${parts[2]}-${parts[1].padStart(2, "0")}-${parts[0].padStart(2, "0")}`;
date = /* @__PURE__ */ new Date(`${dateString}${timePart ? `T${timePart}` : ""}`);
}
}
break;
}
case "yyyy.MM.dd HH:mm:ss": {
const [datePart, timePart] = dateStr.split(" ");
if (datePart) {
const parts = datePart.split(".");
if (parts.length === 3) {
const dateString = `${parts[0]}-${parts[1].padStart(2, "0")}-${parts[2].padStart(2, "0")}`;
date = /* @__PURE__ */ new Date(`${dateString}${timePart ? `T${timePart}` : ""}`);
}
}
break;
}
case "yyyyMMdd": {
if (dateStr.length === 8) {
const year = dateStr.substring(0, 4);
const month = dateStr.substring(4, 6);
const day = dateStr.substring(6, 8);
date = /* @__PURE__ */ new Date(`${year}-${month}-${day}`);
}
break;
}
case "yyyy. MM. dd.": {
const cleaned = dateStr.replace(/\./g, "").trim();
const parts = cleaned.split(/\s+/);
if (parts.length === 3) {
date = /* @__PURE__ */ new Date(`${parts[0]}-${parts[1].padStart(2, "0")}-${parts[2].padStart(2, "0")}`);
}
break;
}
case "ddd MMM dd yyyy": {
const parts = dateStr.split(/\s+/);
if (parts.length >= 4) {
date = /* @__PURE__ */ new Date(`${parts[1]} ${parts[2]} ${parts[3]}`);
}
break;
}
case "ddd MMM dd HH:mm:ss yyyy": {
const parts = dateStr.split(/\s+/);
if (parts.length >= 5) {
date = /* @__PURE__ */ new Date(`${parts[1]} ${parts[2]} ${parts[4]} ${parts[3]}`);
}
break;
}
case "yyyyMMdd HH:mm:ss": {
const [datePart, timePart] = dateStr.split(" ");
if (datePart && datePart.length === 8) {
const year = datePart.substring(0, 4);
const month = datePart.substring(4, 6);
const day = datePart.substring(6, 8);
date = /* @__PURE__ */ new Date(`${year}-${month}-${day}${timePart ? `T${timePart}` : ""}`);
}
break;
}
case "dd-MMM-yyyy HH:mm:ss 'UTC'": {
const cleaned = dateStr.replace(/\s*UTC\s*$/, "");
date = new Date(cleaned.replace(/-/g, " "));
break;
}
case "MMM dd yyyy": {
date = new Date(dateStr);
break;
}
default: {
date = new Date(dateStr);
}
}
if (date && !Number.isNaN(date.getTime())) {
return date;
}
const fallback = new Date(dateStr);
if (!Number.isNaN(fallback.getTime())) {
return fallback;
}
return null;
} catch {
return null;
}
}
function parseWhoisData({ rawData, domain }) {
if (!rawData) {
return { domainName: domain, isAvailable: true };
}
const result = { domainName: domain };
let domainRegex;
if (domain.endsWith(".com") || domain.endsWith(".net") || domain.endsWith(".name")) {
domainRegex = comRegex;
} else if (domain.endsWith(".org") || domain.endsWith(".me") || domain.endsWith(".mobi")) {
domainRegex = orgRegex;
} else if (domain.endsWith(".au")) {
domainRegex = auRegex;
} else if (domain.endsWith(".ru") || domain.endsWith(".\u0440\u0444") || domain.endsWith(".su")) {
domainRegex = ruRegex;
} else if (domain.endsWith(".us") || domain.endsWith(".biz")) {
domainRegex = usRegex;
} else if (domain.endsWith(".uk")) {
domainRegex = ukRegex;
} else if (domain.endsWith(".fr")) {
domainRegex = frRegex;
} else if (domain.endsWith(".nl")) {
domainRegex = nlRegex;
} else if (domain.endsWith(".fi")) {
domainRegex = fiRegex;
} else if (domain.endsWith(".jp")) {
domainRegex = jpRegex;
} else if (domain.endsWith(".pl")) {
domainRegex = plRegex;
} else if (domain.endsWith(".br")) {
domainRegex = brRegex;
} else if (domain.endsWith(".eu")) {
domainRegex = euRegex;
} else if (domain.endsWith(".ee")) {
domainRegex = eeRegex;
} else if (domain.endsWith(".kr")) {
domainRegex = krRegex;
} else if (domain.endsWith(".bg")) {
domainRegex = bgRegex;
} else if (domain.endsWith(".de")) {
domainRegex = deRegex;
} else if (domain.endsWith(".at")) {
domainRegex = atRegex;
} else if (domain.endsWith(".ca")) {
domainRegex = caRegex;
} else if (domain.endsWith(".be")) {
domainRegex = beRegex;
} else if (domain.endsWith(".kg")) {
domainRegex = kgRegex;
} else if (domain.endsWith(".info")) {
domainRegex = infoRegex;
} else if (domain.endsWith(".id")) {
domainRegex = idRegex;
} else if (domain.endsWith(".sk")) {
domainRegex = skRegex;
} else if (domain.endsWith(".se") || domain.endsWith(".nu")) {
domainRegex = seRegex;
} else if (domain.endsWith(".is")) {
domainRegex = isRegex;
} else if (domain.endsWith(".co")) {
domainRegex = coRegex;
} else {
domainRegex = defaultRegex;
}
Object.keys(domainRegex).forEach((key) => {
var _a, _b;
const typedKey = key;
const pattern = domainRegex[typedKey];
if (!pattern || typedKey === "dateFormat")
return;
let regex = new RegExp(pattern, "i");
if (typedKey === "status") {
regex = new RegExp(pattern, "gi");
}
const matches = rawData.match(regex);
if (matches) {
if (typedKey === "rateLimited") {
result.rateLimited = true;
throw new Error("Rate Limited");
} else if (typedKey === "notFound") {
result.isAvailable = true;
} else if (typedKey === "status") {
result.status = [];
let match;
const statusRegex = new RegExp(pattern, "gi");
match = statusRegex.exec(rawData);
while (match !== null) {
if (match[1]) {
result.status.push(match[1].trim());
}
match = statusRegex.exec(rawData);
}
} else if (typedKey === "creationDate" || typedKey === "expirationDate" || typedKey === "updatedDate") {
matches[matches.length - 1];
let dateStr;
if (typedKey === "creationDate" && pattern.includes("Creat(ed|ion)")) {
const creationMatch = rawData.match(/Creat(?:ed|ion) Date:\s*(.+)/i);
dateStr = (_a = creationMatch === null || creationMatch === void 0 ? void 0 : creationMatch[1]) === null || _a === void 0 ? void 0 : _a.trim();
} else {
const extractMatch = rawData.match(new RegExp(pattern, "i"));
dateStr = (_b = extractMatch === null || extractMatch === void 0 ? void 0 : extractMatch[1]) === null || _b === void 0 ? void 0 : _b.trim();
}
if (dateStr) {
const dateFormat = domainRegex.dateFormat || "yyyy-MM-dd'T'HH:mm:ss'Z'";
const parsedDate = parseDate(dateStr, dateFormat);
if (parsedDate) {
result[typedKey] = parsedDate.toISOString();
} else {
result[typedKey] = dateStr;
}
}
} else if (typedKey === "domainName") {
const match = rawData.match(regex);
if (match === null || match === void 0 ? void 0 : match[1]) {
result.domainName = match[1].toLowerCase().trim();
}
} else if (typedKey === "registrar") {
const match = rawData.match(regex);
if (match === null || match === void 0 ? void 0 : match[1]) {
result.registrar = match[1].trim();
}
}
}
});
if (result.isAvailable === void 0) {
result.isAvailable = false;
}
const patterns = [
{ pattern: /Name Server:\s*(.*)/gi, property: "nameServers", multiple: true },
{ pattern: /Registrar URL:\s*(.*)/i, property: "registrarUrl" },
{ pattern: /Registrar WHOIS Server:\s*(.*)/i, property: "registrarWhoisServer" },
{ pattern: /Registry Domain ID:\s*(.*)/i, property: "registryDomainId" },
{ pattern: /Registrar Abuse Contact Email:\s*(.*)/i, property: "registrarAbuseContactEmail" },
{ pattern: /Registrar Abuse Contact Phone:\s*(.*)/i, property: "registrarAbuseContactPhone" },
{ pattern: /DNSSEC:\s*(.*)/i, property: "dnssec" },
{ pattern: /Registrar IANA ID:\s*(.*)/i, property: "registrarIanaId" }
];
patterns.forEach(({ pattern, property, multiple }) => {
if (multiple) {
const matches = Array.from(rawData.matchAll(pattern));
if (matches.length > 0) {
result[property] = matches.map((m) => m[1].trim());
}
} else {
const match = rawData.match(pattern);
if (match === null || match === void 0 ? void 0 : match[1]) {
result[property] = match[1].trim();
}
}
});
return result;
}
const WHOIS_SERVERS = {
com: "whois.verisign-grs.com",
net: "whois.verisign-grs.com",
org: "whois.pir.org",
info: "whois.afilias.net",
biz: "whois.biz",
io: "whois.nic.io",
co: "whois.nic.co",
uk: "whois.nic.uk",
de: "whois.denic.de",
fr: "whois.afnic.fr",
jp: "whois.jprs.jp",
au: "whois.auda.org.au",
ca: "whois.cira.ca",
eu: "whois.eu",
nl: "whois.domain-registry.nl",
ru: "whois.tcinet.ru",
br: "whois.registro.br",
cn: "whois.cnnic.cn",
in: "whois.registry.in",
me: "whois.nic.me",
us: "whois.nic.us",
tv: "whois.nic.tv",
cc: "whois.nic.cc",
ws: "whois.website.ws",
it: "whois.nic.it",
se: "whois.iis.se",
no: "whois.norid.no",
dk: "whois.dk-hostmaster.dk",
fi: "whois.fi",
es: "whois.nic.es",
ch: "whois.nic.ch",
pl: "whois.dns.pl",
be: "whois.dns.be",
at: "whois.nic.at",
ie: "whois.iedr.ie",
pt: "whois.dns.pt",
cz: "whois.nic.cz",
nz: "whois.srs.net.nz",
za: "whois.registry.net.za",
sg: "whois.sgnic.sg",
hk: "whois.hkirc.hk",
kr: "whois.kr",
tw: "whois.twnic.net.tw",
mx: "whois.mx",
ar: "whois.nic.ar",
cl: "whois.nic.cl"
};
function queryWhoisServer(domain, server, timeout = 5e3) {
return new Promise((resolve, reject) => {
const client = new net.Socket();
let data = "";
const timer = setTimeout(() => {
client.destroy();
reject(new Error("WHOIS query timeout"));
}, timeout);
client.connect(43, server, () => {
client.write(`${domain}\r
`);
});
client.on("data", (chunk) => {
data += chunk.toString();
});
client.on("close", () => {
clearTimeout(timer);
resolve(data);
});
client.on("error", (err) => {
clearTimeout(timer);
reject(err);
});
});
}
async function getWhoisData(domain, timeout = 5e3) {
var _a;
const cacheKey = `whois:${domain}`;
const cached = whoisCache.get(cacheKey);
if (cached) {
return cached;
}
try {
const tld = (_a = domain.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (!tld) {
throw new Error("Invalid domain");
}
const whoisServer = WHOIS_SERVERS[tld];
if (!whoisServer) {
const defaultServer = "whois.iana.org";
const ianaResponse = await queryWhoisServer(domain, defaultServer, timeout);
const referMatch = ianaResponse.match(/refer:\s+(\S+)/i);
if (referMatch === null || referMatch === void 0 ? void 0 : referMatch[1]) {
const referredServer = referMatch[1];
const whoisResponse2 = await queryWhoisServer(domain, referredServer, timeout);
const whoisData3 = parseWhoisData({ rawData: whoisResponse2, domain });
whoisCache.set(cacheKey, whoisData3);
return whoisData3;
}
const whoisData2 = parseWhoisData({ rawData: ianaResponse, domain });
whoisCache.set(cacheKey, whoisData2);
return whoisData2;
}
const whoisResponse = await queryWhoisServer(domain, whoisServer, timeout);
const whoisData = parseWhoisData({ rawData: whoisResponse, domain });
whoisCache.set(cacheKey, whoisData);
return whoisData;
} catch (_error) {
return null;
}
}
async function getDomainAge(domain, timeout = 5e3) {
try {
const cleanDomain = domain.replace(/^https?:\/\//, "").split("/")[0].split("@").pop();
if (!cleanDomain) {
return null;
}
if (!psl.isValid(cleanDomain)) {
return null;
}
const whoisData = await getWhoisData(cleanDomain, timeout);
if (!whoisData || !whoisData.creationDate) {
return null;
}
const now = /* @__PURE__ */ new Date();
const creationDate = new Date(whoisData.creationDate);
const ageInMilliseconds = now.getTime() - creationDate.getTime();
const ageInDays = Math.floor(ageInMilliseconds / (1e3 * 60 * 60 * 24));
const ageInYears = ageInDays / 365.25;
return {
domain: cleanDomain,
creationDate,
ageInDays,
ageInYears: parseFloat(ageInYears.toFixed(2)),
expirationDate: whoisData.expirationDate ? new Date(whoisData.expirationDate) : null,
updatedDate: whoisData.updatedDate ? new Date(whoisData.updatedDate) : null
};
} catch (_error) {
return null;
}
}
async function getDomainRegistrationStatus(domain, timeout = 5e3) {
try {
const cleanDomain = domain.replace(/^https?:\/\//, "").split("/")[0].split("@").pop();
if (!cleanDomain) {
return null;
}
if (!psl.isValid(cleanDomain)) {
return null;
}
const whoisData = await getWhoisData(cleanDomain, timeout);
if (!whoisData || whoisData.isAvailable) {
return {
domain: cleanDomain,
isRegistered: false,
isAvailable: true,
status: [],
registrar: null,
nameServers: [],
expirationDate: null,
isExpired: false,
daysUntilExpiration: null,
isPendingDelete: false,
isLocked: false
};
}
const isRegistered = !!(whoisData.domainName || whoisData.creationDate || whoisData.registrar);
let isExpired = false;
let daysUntilExpiration = null;
let expirationDate = null;
if (whoisData.expirationDate) {
expirationDate = new Date(whoisData.expirationDate);
const now = /* @__PURE__ */ new Date();
const expirationTime = expirationDate.getTime();
const currentTime =