@emailcheck/email-validator-js 2.13.1-beta.3 → 2.13.1-beta.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/dns.d.ts +2 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.esm.js +155 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +156 -6
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +24 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -98,12 +98,17 @@ function clearAllCaches() {
|
|
|
98
98
|
whoisCache.clear();
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
async function resolveMxRecords(
|
|
101
|
+
async function resolveMxRecords(params) {
|
|
102
|
+
const { domain, cache, logger } = params;
|
|
103
|
+
const log = logger || (() => {
|
|
104
|
+
});
|
|
102
105
|
const cacheStore = mxCacheStore(cache);
|
|
103
106
|
const cached = await cacheStore.get(domain);
|
|
104
107
|
if (cached !== null && cached !== void 0) {
|
|
108
|
+
log(`[resolveMxRecords] Cache hit for ${domain}: ${cached.length} MX records`);
|
|
105
109
|
return cached;
|
|
106
110
|
}
|
|
111
|
+
log(`[resolveMxRecords] Performing DNS MX lookup for ${domain}`);
|
|
107
112
|
try {
|
|
108
113
|
const records = await node_dns.promises.resolveMx(domain);
|
|
109
114
|
records.sort((a, b) => {
|
|
@@ -116,9 +121,12 @@ async function resolveMxRecords(domain, cache) {
|
|
|
116
121
|
return 0;
|
|
117
122
|
});
|
|
118
123
|
const exchanges = records.map((record) => record.exchange);
|
|
124
|
+
log(`[resolveMxRecords] Found ${exchanges.length} MX records for ${domain}: [${exchanges.join(", ")}]`);
|
|
119
125
|
await cacheStore.set(domain, exchanges);
|
|
126
|
+
log(`[resolveMxRecords] Cached ${exchanges.length} MX records for ${domain}`);
|
|
120
127
|
return exchanges;
|
|
121
128
|
} catch (error) {
|
|
129
|
+
log(`[resolveMxRecords] MX lookup failed for ${domain}, caching empty result`);
|
|
122
130
|
await cacheStore.set(domain, []);
|
|
123
131
|
throw error;
|
|
124
132
|
}
|
|
@@ -1928,6 +1936,106 @@ async function getDomainRegistrationStatus(domain, timeout = 5e3, debug = false)
|
|
|
1928
1936
|
}
|
|
1929
1937
|
}
|
|
1930
1938
|
|
|
1939
|
+
class RedisAdapter {
|
|
1940
|
+
constructor(redis, options = {}) {
|
|
1941
|
+
this.redis = redis;
|
|
1942
|
+
this.keyPrefix = options.keyPrefix || "email_validator:";
|
|
1943
|
+
this.defaultTtlMs = options.defaultTtlMs || 36e5;
|
|
1944
|
+
this.jsonSerializer = options.jsonSerializer || {
|
|
1945
|
+
stringify: (value) => {
|
|
1946
|
+
const processed = this.processDatesForSerialization(value);
|
|
1947
|
+
return JSON.stringify(processed);
|
|
1948
|
+
},
|
|
1949
|
+
parse: (value) => JSON.parse(value, (key, v) => {
|
|
1950
|
+
if (v && typeof v === "object" && v.__type === "Date") {
|
|
1951
|
+
return new Date(v.value);
|
|
1952
|
+
}
|
|
1953
|
+
return v;
|
|
1954
|
+
})
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
getKey(key) {
|
|
1958
|
+
return `${this.keyPrefix}${key}`;
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Recursively process an object to convert Date instances to a serializable format
|
|
1962
|
+
*/
|
|
1963
|
+
processDatesForSerialization(obj) {
|
|
1964
|
+
if (obj instanceof Date) {
|
|
1965
|
+
return { __type: "Date", value: obj.toISOString() };
|
|
1966
|
+
}
|
|
1967
|
+
if (obj && typeof obj === "object") {
|
|
1968
|
+
if (Array.isArray(obj)) {
|
|
1969
|
+
return obj.map((item) => this.processDatesForSerialization(item));
|
|
1970
|
+
}
|
|
1971
|
+
const result = {};
|
|
1972
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1973
|
+
result[key] = this.processDatesForSerialization(value);
|
|
1974
|
+
}
|
|
1975
|
+
return result;
|
|
1976
|
+
}
|
|
1977
|
+
return obj;
|
|
1978
|
+
}
|
|
1979
|
+
async get(key) {
|
|
1980
|
+
try {
|
|
1981
|
+
const value = await this.redis.get(this.getKey(key));
|
|
1982
|
+
if (value === null) {
|
|
1983
|
+
return null;
|
|
1984
|
+
}
|
|
1985
|
+
return this.jsonSerializer.parse(value);
|
|
1986
|
+
} catch (error) {
|
|
1987
|
+
console.error("Redis get error:", error);
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
async set(key, value, ttlMs) {
|
|
1992
|
+
try {
|
|
1993
|
+
const serializedValue = this.jsonSerializer.stringify(value);
|
|
1994
|
+
const ttl = ttlMs || this.defaultTtlMs;
|
|
1995
|
+
const ttlSeconds = Math.ceil(ttl / 1e3);
|
|
1996
|
+
await this.redis.set(this.getKey(key), serializedValue, "EX", ttlSeconds);
|
|
1997
|
+
} catch (error) {
|
|
1998
|
+
console.error("Redis set error:", error);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
async delete(key) {
|
|
2002
|
+
try {
|
|
2003
|
+
const result = await this.redis.del(this.getKey(key));
|
|
2004
|
+
return result > 0;
|
|
2005
|
+
} catch (error) {
|
|
2006
|
+
console.error("Redis delete error:", error);
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
async has(key) {
|
|
2011
|
+
try {
|
|
2012
|
+
const result = await this.redis.exists(this.getKey(key));
|
|
2013
|
+
return result > 0;
|
|
2014
|
+
} catch (error) {
|
|
2015
|
+
console.error("Redis exists error:", error);
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
async clear() {
|
|
2020
|
+
try {
|
|
2021
|
+
await this.redis.flushdb();
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
console.error("Redis clear error:", error);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
// size() is not applicable for Redis as it's a distributed store
|
|
2027
|
+
size() {
|
|
2028
|
+
return void 0;
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Helper method to delete only keys with the configured prefix
|
|
2032
|
+
* Requires Redis SCAN command which might not be available in all Redis clients
|
|
2033
|
+
*/
|
|
2034
|
+
async clearPrefixed() {
|
|
2035
|
+
console.warn("clearPrefixed not implemented. Use clear() to flush the entire database.");
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
|
|
1931
2039
|
async function verifyEmailBatch(params) {
|
|
1932
2040
|
const { emailAddresses, concurrency = 5, timeout = 4e3, verifyMx = true, verifySmtp = false, checkDisposable = true, checkFree = true, detectName = false, nameDetectionMethod, suggestDomain = false, domainSuggestionMethod, commonDomains, skipMxForDisposable = false, skipDomainWhoisForDisposable = false, cache } = params;
|
|
1933
2041
|
const startTime = Date.now();
|
|
@@ -2006,7 +2114,10 @@ function createErrorResult(email, _error) {
|
|
|
2006
2114
|
|
|
2007
2115
|
let disposableEmailProviders;
|
|
2008
2116
|
let freeEmailProviders;
|
|
2009
|
-
async function isDisposableEmail(
|
|
2117
|
+
async function isDisposableEmail(params) {
|
|
2118
|
+
const { emailOrDomain, cache, logger } = params;
|
|
2119
|
+
const log = logger || (() => {
|
|
2120
|
+
});
|
|
2010
2121
|
const parts = emailOrDomain.split("@");
|
|
2011
2122
|
const emailDomain = parts.length > 1 ? parts[1] : parts[0];
|
|
2012
2123
|
if (!emailDomain) {
|
|
@@ -2020,6 +2131,7 @@ async function isDisposableEmail(emailOrDomain, cache) {
|
|
|
2020
2131
|
cached = null;
|
|
2021
2132
|
}
|
|
2022
2133
|
if (cached !== null && cached !== void 0) {
|
|
2134
|
+
log(`[isDisposableEmail] Cache hit for ${emailDomain}: ${cached}`);
|
|
2023
2135
|
return cached;
|
|
2024
2136
|
}
|
|
2025
2137
|
if (!disposableEmailProviders) {
|
|
@@ -2028,11 +2140,17 @@ async function isDisposableEmail(emailOrDomain, cache) {
|
|
|
2028
2140
|
const result = disposableEmailProviders.has(emailDomain);
|
|
2029
2141
|
try {
|
|
2030
2142
|
await cacheStore.set(emailDomain, result);
|
|
2143
|
+
log(`[isDisposableEmail] Cached result for ${emailDomain}: ${result}`);
|
|
2031
2144
|
} catch (_error) {
|
|
2145
|
+
log(`[isDisposableEmail] Cache write error for ${emailDomain}`);
|
|
2032
2146
|
}
|
|
2147
|
+
log(`[isDisposableEmail] Check result for ${emailDomain}: ${result}`);
|
|
2033
2148
|
return result;
|
|
2034
2149
|
}
|
|
2035
|
-
async function isFreeEmail(
|
|
2150
|
+
async function isFreeEmail(params) {
|
|
2151
|
+
const { emailOrDomain, cache, logger } = params;
|
|
2152
|
+
const log = logger || (() => {
|
|
2153
|
+
});
|
|
2036
2154
|
const parts = emailOrDomain.split("@");
|
|
2037
2155
|
const emailDomain = parts.length > 1 ? parts[1] : parts[0];
|
|
2038
2156
|
if (!emailDomain) {
|
|
@@ -2046,6 +2164,7 @@ async function isFreeEmail(emailOrDomain, cache) {
|
|
|
2046
2164
|
cached = null;
|
|
2047
2165
|
}
|
|
2048
2166
|
if (cached !== null && cached !== void 0) {
|
|
2167
|
+
log(`[isFreeEmail] Cache hit for ${emailDomain}: ${cached}`);
|
|
2049
2168
|
return cached;
|
|
2050
2169
|
}
|
|
2051
2170
|
if (!freeEmailProviders) {
|
|
@@ -2054,8 +2173,11 @@ async function isFreeEmail(emailOrDomain, cache) {
|
|
|
2054
2173
|
const result = freeEmailProviders.has(emailDomain);
|
|
2055
2174
|
try {
|
|
2056
2175
|
await cacheStore.set(emailDomain, result);
|
|
2176
|
+
log(`[isFreeEmail] Cached result for ${emailDomain}: ${result}`);
|
|
2057
2177
|
} catch (_error) {
|
|
2178
|
+
log(`[isFreeEmail] Cache write error for ${emailDomain}`);
|
|
2058
2179
|
}
|
|
2180
|
+
log(`[isFreeEmail] Check result for ${emailDomain}: ${result}`);
|
|
2059
2181
|
return result;
|
|
2060
2182
|
}
|
|
2061
2183
|
const domainPorts = {
|
|
@@ -2064,6 +2186,7 @@ const domainPorts = {
|
|
|
2064
2186
|
"ovh.net": 465
|
|
2065
2187
|
};
|
|
2066
2188
|
async function verifyEmail(params) {
|
|
2189
|
+
var _a;
|
|
2067
2190
|
const { emailAddress, timeout = 4e3, verifyMx = true, verifySmtp = false, debug = false, checkDisposable = true, checkFree = true, detectName: detectName2 = false, nameDetectionMethod, suggestDomain: suggestDomain2 = true, domainSuggestionMethod, commonDomains, checkDomainAge = false, checkDomainRegistration = false, whoisTimeout = 5e3, skipMxForDisposable = false, skipDomainWhoisForDisposable = false } = params;
|
|
2068
2191
|
const startTime = Date.now();
|
|
2069
2192
|
const log = debug ? console.debug : (..._args) => {
|
|
@@ -2121,36 +2244,56 @@ async function verifyEmail(params) {
|
|
|
2121
2244
|
return result;
|
|
2122
2245
|
}
|
|
2123
2246
|
if (checkDisposable) {
|
|
2124
|
-
|
|
2247
|
+
log(`[verifyEmail] Checking if ${emailAddress} is disposable email`);
|
|
2248
|
+
result.isDisposable = await isDisposableEmail({ emailOrDomain: emailAddress, cache: params.cache, logger: log });
|
|
2249
|
+
log(`[verifyEmail] Disposable check result: ${result.isDisposable}`);
|
|
2125
2250
|
if (result.isDisposable && result.metadata) {
|
|
2126
2251
|
result.metadata.error = exports.VerificationErrorCode.DISPOSABLE_EMAIL;
|
|
2127
2252
|
}
|
|
2128
2253
|
}
|
|
2129
2254
|
if (checkFree) {
|
|
2130
|
-
|
|
2255
|
+
log(`[verifyEmail] Checking if ${emailAddress} is free email provider`);
|
|
2256
|
+
result.isFree = await isFreeEmail({ emailOrDomain: emailAddress, cache: params.cache, logger: log });
|
|
2257
|
+
log(`[verifyEmail] Free email check result: ${result.isFree}`);
|
|
2131
2258
|
}
|
|
2132
2259
|
const shouldSkipMx = skipMxForDisposable && result.isDisposable;
|
|
2133
2260
|
const shouldSkipDomainWhois = skipDomainWhoisForDisposable && result.isDisposable;
|
|
2261
|
+
if (shouldSkipMx) {
|
|
2262
|
+
log(`[verifyEmail] Skipping MX record check for disposable email: ${emailAddress}`);
|
|
2263
|
+
}
|
|
2264
|
+
if (shouldSkipDomainWhois) {
|
|
2265
|
+
log(`[verifyEmail] Skipping domain WHOIS checks for disposable email: ${emailAddress}`);
|
|
2266
|
+
}
|
|
2134
2267
|
if (checkDomainAge && !shouldSkipDomainWhois) {
|
|
2268
|
+
log(`[verifyEmail] Checking domain age for ${domain}`);
|
|
2135
2269
|
try {
|
|
2136
2270
|
result.domainAge = await getDomainAge(domain, whoisTimeout, debug);
|
|
2271
|
+
log(`[verifyEmail] Domain age result:`, result.domainAge ? `${result.domainAge.ageInDays} days` : "null");
|
|
2137
2272
|
} catch (err) {
|
|
2138
2273
|
log("[verifyEmail] Failed to get domain age", err);
|
|
2139
2274
|
result.domainAge = null;
|
|
2140
2275
|
}
|
|
2276
|
+
} else if (checkDomainAge && shouldSkipDomainWhois) {
|
|
2277
|
+
log(`[verifyEmail] Domain age check skipped due to disposable email and skipDomainWhoisForDisposable=true`);
|
|
2141
2278
|
}
|
|
2142
2279
|
if (checkDomainRegistration && !shouldSkipDomainWhois) {
|
|
2280
|
+
log(`[verifyEmail] Checking domain registration status for ${domain}`);
|
|
2143
2281
|
try {
|
|
2144
2282
|
result.domainRegistration = await getDomainRegistrationStatus(domain, whoisTimeout, debug);
|
|
2283
|
+
log(`[verifyEmail] Domain registration result:`, ((_a = result.domainRegistration) === null || _a === void 0 ? void 0 : _a.isRegistered) ? "registered" : "not registered");
|
|
2145
2284
|
} catch (err) {
|
|
2146
2285
|
log("[verifyEmail] Failed to get domain registration status", err);
|
|
2147
2286
|
result.domainRegistration = null;
|
|
2148
2287
|
}
|
|
2288
|
+
} else if (checkDomainRegistration && shouldSkipDomainWhois) {
|
|
2289
|
+
log(`[verifyEmail] Domain registration check skipped due to disposable email and skipDomainWhoisForDisposable=true`);
|
|
2149
2290
|
}
|
|
2150
2291
|
if ((verifyMx || verifySmtp) && !shouldSkipMx) {
|
|
2292
|
+
log(`[verifyEmail] Checking MX records for ${domain}`);
|
|
2151
2293
|
try {
|
|
2152
|
-
const mxRecords = await resolveMxRecords(domain, params.cache);
|
|
2294
|
+
const mxRecords = await resolveMxRecords({ domain, cache: params.cache, logger: log });
|
|
2153
2295
|
result.validMx = mxRecords.length > 0;
|
|
2296
|
+
log(`[verifyEmail] MX records found: ${mxRecords.length}, valid: ${result.validMx}`);
|
|
2154
2297
|
if (!result.validMx && result.metadata) {
|
|
2155
2298
|
result.metadata.error = exports.VerificationErrorCode.NO_MX_RECORDS;
|
|
2156
2299
|
}
|
|
@@ -2160,6 +2303,7 @@ async function verifyEmail(params) {
|
|
|
2160
2303
|
const cachedSmtp = await smtpCacheInstance.get(cacheKey);
|
|
2161
2304
|
if (cachedSmtp !== null && cachedSmtp !== void 0) {
|
|
2162
2305
|
result.validSmtp = cachedSmtp;
|
|
2306
|
+
log(`[verifyEmail] SMTP result from cache: ${result.validSmtp} for ${emailAddress}`);
|
|
2163
2307
|
if (result.metadata) {
|
|
2164
2308
|
result.metadata.cached = true;
|
|
2165
2309
|
}
|
|
@@ -2170,6 +2314,7 @@ async function verifyEmail(params) {
|
|
|
2170
2314
|
});
|
|
2171
2315
|
}
|
|
2172
2316
|
} else {
|
|
2317
|
+
log(`[verifyEmail] Performing SMTP verification for ${emailAddress}`);
|
|
2173
2318
|
let domainPort = params.smtpPort;
|
|
2174
2319
|
if (!domainPort) {
|
|
2175
2320
|
const mxDomain = psl.parse(mxRecords[0]);
|
|
@@ -2188,6 +2333,7 @@ async function verifyEmail(params) {
|
|
|
2188
2333
|
});
|
|
2189
2334
|
await smtpCacheInstance.set(cacheKey, smtpResult);
|
|
2190
2335
|
result.validSmtp = smtpResult;
|
|
2336
|
+
log(`[verifyEmail] SMTP verification result: ${result.validSmtp} for ${emailAddress} (cached for future use)`);
|
|
2191
2337
|
}
|
|
2192
2338
|
if (result.validSmtp === false && result.metadata) {
|
|
2193
2339
|
result.metadata.error = exports.VerificationErrorCode.MAILBOX_NOT_FOUND;
|
|
@@ -2202,6 +2348,8 @@ async function verifyEmail(params) {
|
|
|
2202
2348
|
result.metadata.error = exports.VerificationErrorCode.NO_MX_RECORDS;
|
|
2203
2349
|
}
|
|
2204
2350
|
}
|
|
2351
|
+
} else if ((verifyMx || verifySmtp) && shouldSkipMx) {
|
|
2352
|
+
log(`[verifyEmail] MX/SMTP checks skipped due to disposable email and skipMxForDisposable=true`);
|
|
2205
2353
|
}
|
|
2206
2354
|
if (result.metadata) {
|
|
2207
2355
|
result.metadata.verificationTime = Date.now() - startTime;
|
|
@@ -2210,6 +2358,8 @@ async function verifyEmail(params) {
|
|
|
2210
2358
|
}
|
|
2211
2359
|
|
|
2212
2360
|
exports.COMMON_EMAIL_DOMAINS = COMMON_EMAIL_DOMAINS;
|
|
2361
|
+
exports.LRUAdapter = LRUAdapter;
|
|
2362
|
+
exports.RedisAdapter = RedisAdapter;
|
|
2213
2363
|
exports.clearAllCaches = clearAllCaches;
|
|
2214
2364
|
exports.defaultDomainSuggestionMethod = defaultDomainSuggestionMethod;
|
|
2215
2365
|
exports.defaultNameDetectionMethod = defaultNameDetectionMethod;
|