@lacspace/email-validate 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -115,7 +115,7 @@ var COMMON_DOMAINS = [
|
|
|
115
115
|
"msn.com"
|
|
116
116
|
];
|
|
117
117
|
var LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
|
|
118
|
-
var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
|
|
118
|
+
var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;
|
|
119
119
|
function splitEmail(email) {
|
|
120
120
|
const at = email.lastIndexOf("@");
|
|
121
121
|
if (at <= 0 || at === email.length - 1) return null;
|
|
@@ -129,7 +129,7 @@ function isValidEmail(email) {
|
|
|
129
129
|
if (!parts) return false;
|
|
130
130
|
const { local, domain } = parts;
|
|
131
131
|
if (local.length > 64 || local.length === 0) return false;
|
|
132
|
-
if (domain.length >
|
|
132
|
+
if (domain.length > 253) return false;
|
|
133
133
|
if (!LOCAL_RE.test(local)) return false;
|
|
134
134
|
if (!DOMAIN_RE.test(domain)) return false;
|
|
135
135
|
return true;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 255) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\n// TLD is either an alphabetic label (com, org, …) or an IDN/punycode A-label\n// (e.g. `xn--p1ai`). The `.` separator stays outside every character class so\n// there is no ambiguous overlap that could cause catastrophic backtracking.\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 253) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -113,7 +113,7 @@ var COMMON_DOMAINS = [
|
|
|
113
113
|
"msn.com"
|
|
114
114
|
];
|
|
115
115
|
var LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
|
|
116
|
-
var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
|
|
116
|
+
var DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;
|
|
117
117
|
function splitEmail(email) {
|
|
118
118
|
const at = email.lastIndexOf("@");
|
|
119
119
|
if (at <= 0 || at === email.length - 1) return null;
|
|
@@ -127,7 +127,7 @@ function isValidEmail(email) {
|
|
|
127
127
|
if (!parts) return false;
|
|
128
128
|
const { local, domain } = parts;
|
|
129
129
|
if (local.length > 64 || local.length === 0) return false;
|
|
130
|
-
if (domain.length >
|
|
130
|
+
if (domain.length > 253) return false;
|
|
131
131
|
if (!LOCAL_RE.test(local)) return false;
|
|
132
132
|
if (!DOMAIN_RE.test(domain)) return false;
|
|
133
133
|
return true;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 255) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/email-validate\n * Smart email validation — beyond a regex.\n *\n * Syntax + disposable/temp-mail detection, role-address detection, free-provider\n * flags, Gmail normalization and \"did you mean?\" typo suggestions.\n *\n * Zero dependencies · isomorphic · fully typed.\n */\n\n/** Common free/consumer email providers. */\nexport const FREE_PROVIDERS: Set<string> = new Set([\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"yahoo.co.in\", \"yahoo.co.uk\",\n \"hotmail.com\", \"outlook.com\", \"live.com\", \"msn.com\", \"icloud.com\", \"me.com\",\n \"aol.com\", \"protonmail.com\", \"proton.me\", \"zoho.com\", \"gmx.com\", \"gmx.net\",\n \"yandex.com\", \"yandex.ru\", \"mail.com\", \"mail.ru\", \"rediffmail.com\",\n]);\n\n/** Well-known disposable / temporary-mail domains. Extend via {@link validateEmail} options. */\nexport const DISPOSABLE_DOMAINS: Set<string> = new Set([\n \"mailinator.com\", \"guerrillamail.com\", \"guerrillamail.info\", \"grr.la\",\n \"10minutemail.com\", \"10minutemail.net\", \"tempmail.com\", \"temp-mail.org\",\n \"throwawaymail.com\", \"yopmail.com\", \"yopmail.fr\", \"getnada.com\", \"nada.email\",\n \"trashmail.com\", \"trashmail.de\", \"sharklasers.com\", \"spam4.me\", \"dispostable.com\",\n \"maildrop.cc\", \"mailnesia.com\", \"mohmal.com\", \"fakeinbox.com\", \"tempinbox.com\",\n \"mintemail.com\", \"mytemp.email\", \"emailondeck.com\", \"burnermail.io\", \"33mail.com\",\n \"spamgourmet.com\", \"tempr.email\", \"discard.email\", \"mailcatch.com\", \"inboxbear.com\",\n \"tempmailo.com\", \"temp-mail.io\", \"1secmail.com\", \"moakt.com\", \"tmpmail.org\",\n \"guerrillamailblock.com\", \"pokemail.net\", \"spambog.com\", \"einrot.com\",\n]);\n\n/** Local-parts that indicate a shared/role mailbox rather than a person. */\nexport const ROLE_LOCALS: Set<string> = new Set([\n \"admin\", \"administrator\", \"info\", \"support\", \"sales\", \"contact\", \"help\",\n \"no-reply\", \"noreply\", \"donotreply\", \"do-not-reply\", \"postmaster\", \"webmaster\",\n \"abuse\", \"billing\", \"hr\", \"jobs\", \"careers\", \"marketing\", \"team\", \"hello\",\n \"office\", \"enquiry\", \"enquiries\", \"service\", \"root\", \"security\", \"privacy\",\n]);\n\n/** Popular domains used for typo suggestions. */\nconst COMMON_DOMAINS = [\n \"gmail.com\", \"googlemail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\",\n \"live.com\", \"icloud.com\", \"aol.com\", \"protonmail.com\", \"zoho.com\",\n \"yandex.com\", \"mail.com\", \"msn.com\",\n];\n\n// Pragmatic RFC-5321-ish address grammar (no comments/quoted-strings; good for real-world use).\nconst LOCAL_RE = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;\n// TLD is either an alphabetic label (com, org, …) or an IDN/punycode A-label\n// (e.g. `xn--p1ai`). The `.` separator stays outside every character class so\n// there is no ambiguous overlap that could cause catastrophic backtracking.\nconst DOMAIN_RE = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+(?:xn--[A-Za-z0-9-]{2,}|[A-Za-z]{2,})$/;\n\nexport interface ValidationOptions {\n /** Extra disposable domains to treat as disposable. */\n extraDisposable?: string[];\n /** Turn off \"did you mean?\" suggestions. */\n suggestions?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n /** Lowercased address; for Gmail, dots and +tags stripped. `null` if invalid. */\n normalized: string | null;\n local: string | null;\n domain: string | null;\n disposable: boolean;\n /** A shared/role mailbox (info@, admin@, …). */\n role: boolean;\n /** A free consumer provider (gmail, yahoo, …). */\n free: boolean;\n /** A likely-correct address if a typo was detected, else `null`. */\n suggestion: string | null;\n /** Why it failed, when `valid` is false. */\n reason?: string;\n}\n\nfunction splitEmail(email: string): { local: string; domain: string } | null {\n const at = email.lastIndexOf(\"@\");\n if (at <= 0 || at === email.length - 1) return null;\n return { local: email.slice(0, at), domain: email.slice(at + 1) };\n}\n\n/** Fast boolean syntax + structure check. */\nexport function isValidEmail(email: string): boolean {\n if (typeof email !== \"string\") return false;\n const trimmed = email.trim();\n if (trimmed.length > 254) return false;\n const parts = splitEmail(trimmed);\n if (!parts) return false;\n const { local, domain } = parts;\n if (local.length > 64 || local.length === 0) return false;\n if (domain.length > 253) return false;\n if (!LOCAL_RE.test(local)) return false;\n if (!DOMAIN_RE.test(domain)) return false;\n return true;\n}\n\nexport function isDisposable(domain: string, extra?: string[]): boolean {\n const d = domain.toLowerCase();\n if (DISPOSABLE_DOMAINS.has(d)) return true;\n return extra ? extra.map((x) => x.toLowerCase()).includes(d) : false;\n}\n\nexport function isRoleAddress(local: string): boolean {\n return ROLE_LOCALS.has(local.toLowerCase());\n}\n\nexport function isFreeProvider(domain: string): boolean {\n return FREE_PROVIDERS.has(domain.toLowerCase());\n}\n\n/** Canonical form: lowercased; Gmail addresses get dots + `+tags` removed. */\nexport function normalizeEmail(email: string): string {\n const parts = splitEmail(email.trim());\n if (!parts) return email.trim().toLowerCase();\n let { local } = parts;\n const domain = parts.domain.toLowerCase();\n local = local.toLowerCase();\n if (domain === \"gmail.com\" || domain === \"googlemail.com\") {\n local = local.split(\"+\")[0]!.replace(/\\./g, \"\");\n return `${local}@gmail.com`;\n }\n const plus = local.indexOf(\"+\");\n if (plus > 0) local = local.slice(0, plus);\n return `${local}@${domain}`;\n}\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n dp[i]![j] = Math.min(dp[i - 1]![j]! + 1, dp[i]![j - 1]! + 1, dp[i - 1]![j - 1]! + cost);\n }\n }\n return dp[m]![n]!;\n}\n\n/** Suggest a corrected address when the domain looks like a typo of a common one. */\nexport function suggestEmail(email: string): string | null {\n const parts = splitEmail(email.trim().toLowerCase());\n if (!parts) return null;\n const { local, domain } = parts;\n if (COMMON_DOMAINS.includes(domain)) return null;\n let best: string | null = null;\n let bestDist = Infinity;\n for (const candidate of COMMON_DOMAINS) {\n const dist = levenshtein(domain, candidate);\n if (dist < bestDist) {\n bestDist = dist;\n best = candidate;\n }\n }\n // Only suggest for close typos, and never \"correct\" a longer legit domain.\n if (best && bestDist > 0 && bestDist <= 2 && Math.abs(domain.length - best.length) <= 2) {\n return `${local}@${best}`;\n }\n return null;\n}\n\n/** Full analysis of an email address. */\nexport function validateEmail(email: string, opts: ValidationOptions = {}): ValidationResult {\n const raw = typeof email === \"string\" ? email.trim() : \"\";\n const parts = splitEmail(raw);\n\n if (!isValidEmail(raw) || !parts) {\n return {\n valid: false,\n normalized: null,\n local: null,\n domain: null,\n disposable: false,\n role: false,\n free: false,\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n reason: \"invalid syntax\",\n };\n }\n\n const local = parts.local;\n const domain = parts.domain.toLowerCase();\n return {\n valid: true,\n normalized: normalizeEmail(raw),\n local,\n domain,\n disposable: isDisposable(domain, opts.extraDisposable),\n role: isRoleAddress(local),\n free: isFreeProvider(domain),\n suggestion: opts.suggestions === false ? null : suggestEmail(raw),\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/email-validate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Smart email validation — syntax, disposable/temp-mail detection, role & free-provider flags, Gmail normalization and 'did you mean?' typo suggestions. Zero-dependency, isomorphic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|