email-validator-ultimate
Version:
Advanced email validation library for Node.js. RFC 5322 format validation, MX records, SMTP inbox check, disposable email detection, typo suggestions, risk analysis, Gravatar detection, DNS blacklist check, batch validation, caching, and CLI tool.
205 lines (204 loc) • 6.05 kB
JavaScript
;
/**
* Email typo suggestion module
* Detects common typos in email domains and suggests corrections
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getEmailSuggestion = getEmailSuggestion;
exports.hasTypo = hasTypo;
exports.getSuggestedEmail = getSuggestedEmail;
// Common domain typos and their corrections
const DOMAIN_TYPOS = {
// Gmail typos
'gmial.com': 'gmail.com',
'gmal.com': 'gmail.com',
'gmaill.com': 'gmail.com',
'gmali.com': 'gmail.com',
'gnail.com': 'gmail.com',
'gmaol.com': 'gmail.com',
'gamil.com': 'gmail.com',
'gemail.com': 'gmail.com',
'gmail.con': 'gmail.com',
'gmail.co': 'gmail.com',
'gmail.om': 'gmail.com',
'gmail.cm': 'gmail.com',
'gmai.com': 'gmail.com',
'gmailcom': 'gmail.com',
'g]mail.com': 'gmail.com',
// Yahoo typos
'yaho.com': 'yahoo.com',
'yahooo.com': 'yahoo.com',
'yhaoo.com': 'yahoo.com',
'yhoo.com': 'yahoo.com',
'yahoo.con': 'yahoo.com',
'yahoo.co': 'yahoo.com',
'yaoo.com': 'yahoo.com',
'yahho.com': 'yahoo.com',
'yaooh.com': 'yahoo.com',
// Hotmail typos
'hotmal.com': 'hotmail.com',
'hotmial.com': 'hotmail.com',
'hotmil.com': 'hotmail.com',
'hotmaill.com': 'hotmail.com',
'hotmail.con': 'hotmail.com',
'hotmail.co': 'hotmail.com',
'htmail.com': 'hotmail.com',
'hotnail.com': 'hotmail.com',
'hotamil.com': 'hotmail.com',
// Outlook typos
'outlok.com': 'outlook.com',
'outllok.com': 'outlook.com',
'outlook.con': 'outlook.com',
'outlook.co': 'outlook.com',
'outloook.com': 'outlook.com',
'outook.com': 'outlook.com',
'outlool.com': 'outlook.com',
'oultook.com': 'outlook.com',
// iCloud typos
'iclod.com': 'icloud.com',
'icloud.con': 'icloud.com',
'icould.com': 'icloud.com',
'iclould.com': 'icloud.com',
'icluod.com': 'icloud.com',
// ProtonMail typos
'protonmal.com': 'protonmail.com',
'protonmail.con': 'protonmail.com',
'protonmial.com': 'protonmail.com',
'protonmai.com': 'protonmail.com',
// Common TLD typos
'.con': '.com',
'.cmo': '.com',
'.ocm': '.com',
'.vom': '.com',
'.xom': '.com',
'.cpm': '.com',
'.comn': '.com',
'.comm': '.com',
'.ent': '.net',
'.nte': '.net',
'.ogr': '.org',
'.orgg': '.org',
};
// Popular domains for fuzzy matching
const POPULAR_DOMAINS = [
'gmail.com',
'yahoo.com',
'hotmail.com',
'outlook.com',
'icloud.com',
'protonmail.com',
'aol.com',
'live.com',
'msn.com',
'mail.com',
'zoho.com',
'yandex.com',
'gmx.com',
'fastmail.com',
];
/**
* Calculate Levenshtein distance between two strings
*/
function levenshteinDistance(str1, str2) {
const m = str1.length;
const n = str2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (let i = 0; i <= m; i++)
dp[i][0] = i;
for (let j = 0; j <= n; j++)
dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (str1[i - 1] === str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
else {
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
}
}
}
return dp[m][n];
}
/**
* Find the closest matching domain using Levenshtein distance
*/
function findClosestDomain(domain, maxDistance = 2) {
const normalizedDomain = domain.toLowerCase();
// If domain is already a popular domain, no suggestion needed
if (POPULAR_DOMAINS.includes(normalizedDomain)) {
return null;
}
let closestDomain = null;
let minDistance = Infinity;
for (const popularDomain of POPULAR_DOMAINS) {
const distance = levenshteinDistance(normalizedDomain, popularDomain);
if (distance > 0 && distance <= maxDistance && distance < minDistance) {
minDistance = distance;
closestDomain = popularDomain;
}
}
return closestDomain;
}
/**
* Get email suggestion for potential typos
*/
function getEmailSuggestion(email) {
const result = {
hasSuggestion: false,
original: email,
};
const atIndex = email.lastIndexOf('@');
if (atIndex === -1) {
return result;
}
const localPart = email.substring(0, atIndex);
const domain = email.substring(atIndex + 1).toLowerCase();
// Check direct typo mapping first
if (DOMAIN_TYPOS[domain]) {
const correctedDomain = DOMAIN_TYPOS[domain];
result.hasSuggestion = true;
result.suggested = `${localPart}@${correctedDomain}`;
result.domain = {
original: domain,
suggested: correctedDomain,
};
return result;
}
// Check for TLD typos
for (const [typo, correction] of Object.entries(DOMAIN_TYPOS)) {
if (typo.startsWith('.') && domain.endsWith(typo)) {
const correctedDomain = domain.slice(0, -typo.length) + correction;
result.hasSuggestion = true;
result.suggested = `${localPart}@${correctedDomain}`;
result.domain = {
original: domain,
suggested: correctedDomain,
};
return result;
}
}
// Use fuzzy matching for unknown domains
const closestDomain = findClosestDomain(domain);
if (closestDomain && closestDomain !== domain) {
result.hasSuggestion = true;
result.suggested = `${localPart}@${closestDomain}`;
result.domain = {
original: domain,
suggested: closestDomain,
};
}
return result;
}
/**
* Check if email domain has a typo
*/
function hasTypo(email) {
return getEmailSuggestion(email).hasSuggestion;
}
/**
* Get suggested email correction (returns original if no typo detected)
*/
function getSuggestedEmail(email) {
const suggestion = getEmailSuggestion(email);
return suggestion.suggested || email;
}