@mikkelscheike/email-provider-links 5.0.0 → 5.1.1
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/README.md +150 -23
- package/dist/alias-detection.js +100 -7
- package/dist/api.d.ts +82 -58
- package/dist/api.js +227 -168
- package/dist/constants.d.ts +26 -0
- package/dist/constants.js +29 -0
- package/dist/error-utils.d.ts +56 -0
- package/dist/error-utils.js +89 -0
- package/dist/hash-verifier.js +1 -1
- package/dist/idn.js +10 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +16 -6
- package/dist/provider-loader.d.ts +49 -0
- package/dist/provider-loader.js +129 -19
- package/dist/url-validator.d.ts +5 -0
- package/dist/url-validator.js +13 -0
- package/package.json +3 -2
- package/dist/loader.d.ts +0 -45
- package/dist/loader.js +0 -121
package/dist/api.js
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
* Clean function names and enhanced error context.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.Config = void 0;
|
|
9
|
+
exports.Config = exports.emailsMatch = exports.normalizeEmail = void 0;
|
|
10
10
|
exports.getEmailProvider = getEmailProvider;
|
|
11
11
|
exports.getEmailProviderSync = getEmailProviderSync;
|
|
12
|
-
exports.normalizeEmail = normalizeEmail;
|
|
13
|
-
exports.emailsMatch = emailsMatch;
|
|
14
12
|
exports.getEmailProviderFast = getEmailProviderFast;
|
|
15
13
|
const concurrent_dns_1 = require("./concurrent-dns");
|
|
16
14
|
const provider_loader_1 = require("./provider-loader");
|
|
17
15
|
const idn_1 = require("./idn");
|
|
16
|
+
const alias_detection_1 = require("./alias-detection");
|
|
18
17
|
let cachedProvidersRef = null;
|
|
19
18
|
let cachedDomainMap = null;
|
|
20
19
|
function getDomainMapFromProviders(providers) {
|
|
@@ -82,6 +81,18 @@ function validateAndParseEmailForLookup(email) {
|
|
|
82
81
|
const domain = (0, idn_1.domainToPunycode)(domainRaw);
|
|
83
82
|
return { ok: true, trimmedEmail, domain };
|
|
84
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Convert a full EmailProvider to a simplified version
|
|
86
|
+
*/
|
|
87
|
+
function simplifyProvider(provider) {
|
|
88
|
+
if (!provider)
|
|
89
|
+
return null;
|
|
90
|
+
return {
|
|
91
|
+
companyProvider: provider.companyProvider,
|
|
92
|
+
loginUrl: provider.loginUrl,
|
|
93
|
+
type: provider.type
|
|
94
|
+
};
|
|
95
|
+
}
|
|
85
96
|
/**
|
|
86
97
|
* Get email provider information for any email address.
|
|
87
98
|
*
|
|
@@ -90,16 +101,22 @@ function validateAndParseEmailForLookup(email) {
|
|
|
90
101
|
* - Business domains (mycompany.com using Google Workspace, etc.)
|
|
91
102
|
* - Unknown providers (graceful fallback)
|
|
92
103
|
*
|
|
104
|
+
* By default, returns a simplified response with only essential fields.
|
|
105
|
+
* Use the `extended` option to get full provider details including domains and alias configuration.
|
|
106
|
+
*
|
|
93
107
|
* @param email - The email address to analyze
|
|
94
|
-
* @param
|
|
95
|
-
* @returns Promise resolving to
|
|
108
|
+
* @param options - Optional configuration: timeout for DNS queries (default: 5000ms) and extended response flag
|
|
109
|
+
* @returns Promise resolving to SimplifiedEmailProviderResult (default) or EmailProviderResult (if extended)
|
|
96
110
|
*
|
|
97
111
|
* @example
|
|
98
112
|
* ```typescript
|
|
99
|
-
* //
|
|
100
|
-
* const result = await getEmailProvider('
|
|
101
|
-
*
|
|
102
|
-
*
|
|
113
|
+
* // Default: Simplified response (recommended for frontend)
|
|
114
|
+
* const result = await getEmailProvider('user@gmail.com');
|
|
115
|
+
* // Returns: { provider: { companyProvider, loginUrl, type }, email, loginUrl, detectionMethod }
|
|
116
|
+
*
|
|
117
|
+
* // Extended response (includes domains, alias config, etc.)
|
|
118
|
+
* const extended = await getEmailProvider('user@gmail.com', { extended: true });
|
|
119
|
+
* // Returns: { provider: { companyProvider, loginUrl, domains, alias, type, ... }, ... }
|
|
103
120
|
*
|
|
104
121
|
* // Business domain
|
|
105
122
|
* const business = await getEmailProvider('local@business.tld');
|
|
@@ -112,21 +129,34 @@ function validateAndParseEmailForLookup(email) {
|
|
|
112
129
|
* console.log(invalid.error?.message); // "Invalid email format"
|
|
113
130
|
* ```
|
|
114
131
|
*/
|
|
115
|
-
async function getEmailProvider(email,
|
|
132
|
+
async function getEmailProvider(email, options) {
|
|
133
|
+
// Parse options - support both legacy (number) and new (object) format
|
|
134
|
+
const timeout = typeof options === 'number' ? options : options?.timeout;
|
|
135
|
+
const extended = typeof options === 'object' && options?.extended === true;
|
|
116
136
|
try {
|
|
117
137
|
const parsed = validateAndParseEmailForLookup(email);
|
|
118
138
|
if (!parsed.ok) {
|
|
119
|
-
|
|
139
|
+
// Try to normalize even invalid emails (may help with some edge cases)
|
|
140
|
+
let normalizedEmail = parsed.email;
|
|
141
|
+
try {
|
|
142
|
+
normalizedEmail = (0, alias_detection_1.normalizeEmail)(parsed.email);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// If normalization fails, use original email
|
|
146
|
+
}
|
|
147
|
+
const errorResult = {
|
|
120
148
|
provider: null,
|
|
121
|
-
email:
|
|
122
|
-
loginUrl: null,
|
|
149
|
+
email: normalizedEmail,
|
|
150
|
+
...(extended ? { loginUrl: null } : {}),
|
|
123
151
|
error: parsed.error
|
|
124
152
|
};
|
|
153
|
+
return extended ? errorResult : errorResult;
|
|
125
154
|
}
|
|
126
155
|
const domain = parsed.domain;
|
|
127
156
|
// First try synchronous domain matching
|
|
128
|
-
const syncResult = getEmailProviderSync(email);
|
|
157
|
+
const syncResult = getEmailProviderSync(email, { extended });
|
|
129
158
|
if (syncResult.provider) {
|
|
159
|
+
// Email is already normalized in getEmailProviderSync
|
|
130
160
|
return {
|
|
131
161
|
...syncResult,
|
|
132
162
|
detectionMethod: 'domain_match'
|
|
@@ -135,15 +165,16 @@ async function getEmailProvider(email, timeout) {
|
|
|
135
165
|
// Fall back to DNS detection for business domains
|
|
136
166
|
const loadResult = (0, provider_loader_1.loadProviders)();
|
|
137
167
|
if (!loadResult.success) {
|
|
138
|
-
|
|
168
|
+
const errorResult = {
|
|
139
169
|
provider: null,
|
|
140
170
|
email,
|
|
141
|
-
loginUrl: null,
|
|
171
|
+
...(extended ? { loginUrl: null } : {}),
|
|
142
172
|
error: {
|
|
143
173
|
type: 'NETWORK_ERROR',
|
|
144
174
|
message: 'Service temporarily unavailable'
|
|
145
175
|
}
|
|
146
176
|
};
|
|
177
|
+
return extended ? errorResult : errorResult;
|
|
147
178
|
}
|
|
148
179
|
const providers = loadResult.providers;
|
|
149
180
|
const concurrentResult = await (0, concurrent_dns_1.detectProviderConcurrent)(domain, providers, {
|
|
@@ -151,17 +182,42 @@ async function getEmailProvider(email, timeout) {
|
|
|
151
182
|
enableParallel: true,
|
|
152
183
|
collectDebugInfo: false
|
|
153
184
|
});
|
|
185
|
+
// Normalize email using alias detection (even if no provider found)
|
|
186
|
+
// This ensures consistent email format regardless of provider detection result
|
|
187
|
+
let normalizedEmail = email;
|
|
188
|
+
try {
|
|
189
|
+
normalizedEmail = (0, alias_detection_1.normalizeEmail)(email);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// If normalization fails, use original email
|
|
193
|
+
}
|
|
194
|
+
if (extended) {
|
|
195
|
+
const result = {
|
|
196
|
+
provider: concurrentResult.provider,
|
|
197
|
+
email: normalizedEmail,
|
|
198
|
+
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
199
|
+
detectionMethod: concurrentResult.detectionMethod || 'mx_record'
|
|
200
|
+
};
|
|
201
|
+
if (concurrentResult.proxyService) {
|
|
202
|
+
result.proxyService = concurrentResult.proxyService;
|
|
203
|
+
}
|
|
204
|
+
// Add error context for null results
|
|
205
|
+
if (!result.provider && !result.proxyService) {
|
|
206
|
+
result.error = {
|
|
207
|
+
type: 'UNKNOWN_DOMAIN',
|
|
208
|
+
message: `No email provider found for domain: ${domain}`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
// Default: simplified response
|
|
154
214
|
const result = {
|
|
155
|
-
provider: concurrentResult.provider,
|
|
156
|
-
email,
|
|
157
|
-
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
215
|
+
provider: simplifyProvider(concurrentResult.provider),
|
|
216
|
+
email: normalizedEmail,
|
|
158
217
|
detectionMethod: concurrentResult.detectionMethod || 'mx_record'
|
|
159
218
|
};
|
|
160
|
-
if (concurrentResult.proxyService) {
|
|
161
|
-
result.proxyService = concurrentResult.proxyService;
|
|
162
|
-
}
|
|
163
219
|
// Add error context for null results
|
|
164
|
-
if (!result.provider
|
|
220
|
+
if (!result.provider) {
|
|
165
221
|
result.error = {
|
|
166
222
|
type: 'UNKNOWN_DOMAIN',
|
|
167
223
|
message: `No email provider found for domain: ${domain}`
|
|
@@ -171,40 +227,36 @@ async function getEmailProvider(email, timeout) {
|
|
|
171
227
|
}
|
|
172
228
|
catch (error) {
|
|
173
229
|
// Enhanced error handling
|
|
230
|
+
const errorResult = {
|
|
231
|
+
provider: null,
|
|
232
|
+
email,
|
|
233
|
+
error: {}
|
|
234
|
+
};
|
|
235
|
+
if (extended) {
|
|
236
|
+
errorResult.loginUrl = null;
|
|
237
|
+
}
|
|
174
238
|
if (error instanceof Error && error.message.includes('Rate limit exceeded')) {
|
|
175
239
|
const retryMatch = error.message.match(/Try again in (\d+) seconds/);
|
|
176
240
|
const retryAfter = retryMatch?.[1] ? parseInt(retryMatch[1], 10) : undefined;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
error: {
|
|
182
|
-
type: 'RATE_LIMITED',
|
|
183
|
-
message: 'DNS query rate limit exceeded',
|
|
184
|
-
...(retryAfter !== undefined ? { retryAfter } : {})
|
|
185
|
-
}
|
|
241
|
+
errorResult.error = {
|
|
242
|
+
type: 'RATE_LIMITED',
|
|
243
|
+
message: 'DNS query rate limit exceeded',
|
|
244
|
+
...(retryAfter !== undefined ? { retryAfter } : {})
|
|
186
245
|
};
|
|
246
|
+
return extended ? errorResult : errorResult;
|
|
187
247
|
}
|
|
188
248
|
if (error instanceof Error && error.message.includes('timeout')) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
loginUrl: null,
|
|
193
|
-
error: {
|
|
194
|
-
type: 'DNS_TIMEOUT',
|
|
195
|
-
message: `DNS lookup timed out after ${timeout || 5000}ms`
|
|
196
|
-
}
|
|
249
|
+
errorResult.error = {
|
|
250
|
+
type: 'DNS_TIMEOUT',
|
|
251
|
+
message: `DNS lookup timed out after ${timeout || 5000}ms`
|
|
197
252
|
};
|
|
253
|
+
return extended ? errorResult : errorResult;
|
|
198
254
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
loginUrl: null,
|
|
203
|
-
error: {
|
|
204
|
-
type: 'NETWORK_ERROR',
|
|
205
|
-
message: error instanceof Error ? error.message : 'Unknown network error'
|
|
206
|
-
}
|
|
255
|
+
errorResult.error = {
|
|
256
|
+
type: 'NETWORK_ERROR',
|
|
257
|
+
message: error instanceof Error ? error.message : 'Unknown network error'
|
|
207
258
|
};
|
|
259
|
+
return extended ? errorResult : errorResult;
|
|
208
260
|
}
|
|
209
261
|
}
|
|
210
262
|
/**
|
|
@@ -213,14 +265,22 @@ async function getEmailProvider(email, timeout) {
|
|
|
213
265
|
* This function only checks predefined domains and returns immediately.
|
|
214
266
|
* Use this when you can't use async functions or don't want DNS lookups.
|
|
215
267
|
*
|
|
268
|
+
* By default, returns a simplified response with only essential fields.
|
|
269
|
+
* Use the `extended` option to get full provider details including domains and alias configuration.
|
|
270
|
+
*
|
|
216
271
|
* @param email - The email address to analyze
|
|
217
|
-
* @
|
|
272
|
+
* @param options - Optional configuration: extended response flag
|
|
273
|
+
* @returns SimplifiedEmailProviderResult (default) or EmailProviderResult (if extended) with provider info (limited to known domains)
|
|
218
274
|
*
|
|
219
275
|
* @example
|
|
220
276
|
* ```typescript
|
|
221
|
-
* //
|
|
277
|
+
* // Default: Simplified response (recommended for frontend)
|
|
222
278
|
* const gmail = getEmailProviderSync('user@gmail.com');
|
|
223
|
-
*
|
|
279
|
+
* // Returns: { provider: { companyProvider, loginUrl, type }, email, loginUrl }
|
|
280
|
+
*
|
|
281
|
+
* // Extended response (includes domains, alias config, etc.)
|
|
282
|
+
* const extended = getEmailProviderSync('user@gmail.com', { extended: true });
|
|
283
|
+
* // Returns: { provider: { companyProvider, loginUrl, domains, alias, type, ... }, ... }
|
|
224
284
|
*
|
|
225
285
|
* // Unknown domains return null
|
|
226
286
|
* const unknown = getEmailProviderSync('user@mycompany.com');
|
|
@@ -228,16 +288,28 @@ async function getEmailProvider(email, timeout) {
|
|
|
228
288
|
* console.log(unknown.error?.type); // "UNKNOWN_DOMAIN"
|
|
229
289
|
* ```
|
|
230
290
|
*/
|
|
231
|
-
function getEmailProviderSync(email) {
|
|
291
|
+
function getEmailProviderSync(email, options) {
|
|
292
|
+
const extended = options?.extended === true;
|
|
232
293
|
try {
|
|
233
294
|
const parsed = validateAndParseEmailForLookup(email);
|
|
234
295
|
if (!parsed.ok) {
|
|
235
|
-
|
|
296
|
+
// Try to normalize even invalid emails (may help with some edge cases)
|
|
297
|
+
let normalizedEmail = parsed.email;
|
|
298
|
+
try {
|
|
299
|
+
normalizedEmail = (0, alias_detection_1.normalizeEmail)(parsed.email);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// If normalization fails, use original email
|
|
303
|
+
}
|
|
304
|
+
const errorResult = {
|
|
236
305
|
provider: null,
|
|
237
|
-
email:
|
|
238
|
-
loginUrl: null,
|
|
306
|
+
email: normalizedEmail,
|
|
239
307
|
error: parsed.error
|
|
240
308
|
};
|
|
309
|
+
if (extended) {
|
|
310
|
+
errorResult.loginUrl = null;
|
|
311
|
+
}
|
|
312
|
+
return extended ? errorResult : errorResult;
|
|
241
313
|
}
|
|
242
314
|
const domain = parsed.domain;
|
|
243
315
|
// Load providers with verification
|
|
@@ -249,15 +321,18 @@ function getEmailProviderSync(email) {
|
|
|
249
321
|
if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
|
|
250
322
|
console.error('🚨 Provider lookup blocked due to validation failure');
|
|
251
323
|
}
|
|
252
|
-
|
|
324
|
+
const errorResult = {
|
|
253
325
|
provider: null,
|
|
254
326
|
email,
|
|
255
|
-
loginUrl: null,
|
|
256
327
|
error: {
|
|
257
328
|
type: 'NETWORK_ERROR',
|
|
258
329
|
message: 'Service temporarily unavailable'
|
|
259
330
|
}
|
|
260
331
|
};
|
|
332
|
+
if (extended) {
|
|
333
|
+
errorResult.loginUrl = null;
|
|
334
|
+
}
|
|
335
|
+
return extended ? errorResult : errorResult;
|
|
261
336
|
}
|
|
262
337
|
const domainMap = getDomainMapFromProviders(result.providers);
|
|
263
338
|
provider = domainMap.get(domain) || null;
|
|
@@ -266,20 +341,48 @@ function getEmailProviderSync(email) {
|
|
|
266
341
|
if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
|
|
267
342
|
console.error('🚨 Provider lookup failed:', error);
|
|
268
343
|
}
|
|
269
|
-
|
|
344
|
+
const errorResult = {
|
|
270
345
|
provider: null,
|
|
271
346
|
email,
|
|
272
|
-
loginUrl: null,
|
|
273
347
|
error: {
|
|
274
348
|
type: 'NETWORK_ERROR',
|
|
275
349
|
message: 'Service temporarily unavailable'
|
|
276
350
|
}
|
|
277
351
|
};
|
|
352
|
+
if (extended) {
|
|
353
|
+
errorResult.loginUrl = null;
|
|
354
|
+
}
|
|
355
|
+
return extended ? errorResult : errorResult;
|
|
278
356
|
}
|
|
357
|
+
// Normalize email using alias detection (even if no provider found)
|
|
358
|
+
// This ensures consistent email format regardless of provider detection result
|
|
359
|
+
let normalizedEmail = email;
|
|
360
|
+
try {
|
|
361
|
+
normalizedEmail = (0, alias_detection_1.normalizeEmail)(email);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// If normalization fails, use original email
|
|
365
|
+
}
|
|
366
|
+
if (extended) {
|
|
367
|
+
const result = {
|
|
368
|
+
provider: provider || null,
|
|
369
|
+
email: normalizedEmail,
|
|
370
|
+
loginUrl: provider?.loginUrl || null,
|
|
371
|
+
detectionMethod: 'domain_match'
|
|
372
|
+
};
|
|
373
|
+
// Add error context for null results
|
|
374
|
+
if (!result.provider) {
|
|
375
|
+
result.error = {
|
|
376
|
+
type: 'UNKNOWN_DOMAIN',
|
|
377
|
+
message: `No email provider found for domain: ${domain} (sync mode - business domains not supported)`
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
// Default: simplified response
|
|
279
383
|
const result = {
|
|
280
|
-
provider: provider
|
|
281
|
-
email,
|
|
282
|
-
loginUrl: provider?.loginUrl || null,
|
|
384
|
+
provider: simplifyProvider(provider),
|
|
385
|
+
email: normalizedEmail,
|
|
283
386
|
detectionMethod: 'domain_match'
|
|
284
387
|
};
|
|
285
388
|
// Add error context for null results
|
|
@@ -292,129 +395,56 @@ function getEmailProviderSync(email) {
|
|
|
292
395
|
return result;
|
|
293
396
|
}
|
|
294
397
|
catch (error) {
|
|
295
|
-
|
|
398
|
+
const errorResult = {
|
|
296
399
|
provider: null,
|
|
297
400
|
email,
|
|
298
|
-
loginUrl: null,
|
|
299
401
|
error: {
|
|
300
402
|
type: 'INVALID_EMAIL',
|
|
301
403
|
message: error instanceof Error ? error.message : 'Invalid email address'
|
|
302
404
|
}
|
|
303
405
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Normalize an email address to its canonical form.
|
|
308
|
-
*
|
|
309
|
-
* This handles provider-specific aliasing rules:
|
|
310
|
-
* - Gmail: removes dots and plus addressing
|
|
311
|
-
* - Other providers: removes plus addressing only
|
|
312
|
-
*
|
|
313
|
-
* @param email - The email address to normalize
|
|
314
|
-
* @returns The canonical email address
|
|
315
|
-
*
|
|
316
|
-
* @example
|
|
317
|
-
* ```typescript
|
|
318
|
-
* const canonical = normalizeEmail('L.O.C.A.L+work@DOMAIN.TLD');
|
|
319
|
-
* console.log(canonical); // 'local@domain.tld'
|
|
320
|
-
*
|
|
321
|
-
* const provider = normalizeEmail('local+newsletter@provider.tld');
|
|
322
|
-
* console.log(provider); // 'local@provider.tld'
|
|
323
|
-
* ```
|
|
324
|
-
*/
|
|
325
|
-
function normalizeEmail(email) {
|
|
326
|
-
if (!email || typeof email !== 'string') {
|
|
327
|
-
return email;
|
|
328
|
-
}
|
|
329
|
-
// Convert to lowercase
|
|
330
|
-
const lowercaseEmail = email.toLowerCase().trim();
|
|
331
|
-
// Split email into local and domain parts
|
|
332
|
-
const atIndex = lowercaseEmail.lastIndexOf('@');
|
|
333
|
-
if (atIndex === -1) {
|
|
334
|
-
return lowercaseEmail;
|
|
335
|
-
}
|
|
336
|
-
let localPart = lowercaseEmail.slice(0, atIndex);
|
|
337
|
-
const domainPart = (0, idn_1.domainToPunycode)(lowercaseEmail.slice(atIndex + 1));
|
|
338
|
-
// Use providers for domain lookup
|
|
339
|
-
let provider = null;
|
|
340
|
-
try {
|
|
341
|
-
const result = (0, provider_loader_1.loadProviders)();
|
|
342
|
-
if (!result.success) {
|
|
343
|
-
return lowercaseEmail; // Return as-is if providers can't be loaded
|
|
406
|
+
if (extended) {
|
|
407
|
+
errorResult.loginUrl = null;
|
|
344
408
|
}
|
|
345
|
-
|
|
346
|
-
provider = domainMap.get(domainPart) || null;
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
return lowercaseEmail; // Return as-is if error occurs
|
|
409
|
+
return extended ? errorResult : errorResult;
|
|
350
410
|
}
|
|
351
|
-
if (provider?.alias) {
|
|
352
|
-
// Provider supports aliasing
|
|
353
|
-
if (provider.alias.dots) {
|
|
354
|
-
// Remove all dots from local part (e.g. Gmail)
|
|
355
|
-
localPart = localPart.replace(/\./g, '');
|
|
356
|
-
}
|
|
357
|
-
if (provider.alias.plus) {
|
|
358
|
-
// Remove plus addressing (everything after +)
|
|
359
|
-
const plusIndex = localPart.indexOf('+');
|
|
360
|
-
if (plusIndex !== -1) {
|
|
361
|
-
localPart = localPart.slice(0, plusIndex);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return `${localPart}@${domainPart}`;
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Check if two email addresses are the same person (accounting for aliases).
|
|
369
|
-
*
|
|
370
|
-
* This normalizes both emails and compares their canonical forms.
|
|
371
|
-
* Useful for preventing duplicate accounts and matching login attempts.
|
|
372
|
-
*
|
|
373
|
-
* @param email1 - First email address
|
|
374
|
-
* @param email2 - Second email address
|
|
375
|
-
* @returns true if the emails represent the same person
|
|
376
|
-
*
|
|
377
|
-
* @example
|
|
378
|
-
* ```typescript
|
|
379
|
-
* const match = emailsMatch('local@domain.tld', 'l.o.c.a.l+work@domain.tld');
|
|
380
|
-
* console.log(match); // true
|
|
381
|
-
*
|
|
382
|
-
* const different = emailsMatch('local@domain.tld', 'other@domain.tld');
|
|
383
|
-
* console.log(different); // false
|
|
384
|
-
* ```
|
|
385
|
-
*/
|
|
386
|
-
function emailsMatch(email1, email2) {
|
|
387
|
-
if (!email1 || !email2 || typeof email1 !== 'string' || typeof email2 !== 'string') {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
// Normalize both emails and compare
|
|
391
|
-
const normalized1 = normalizeEmail(email1);
|
|
392
|
-
const normalized2 = normalizeEmail(email2);
|
|
393
|
-
return normalized1 === normalized2;
|
|
394
411
|
}
|
|
412
|
+
// Re-export alias detection functions from the dedicated module
|
|
413
|
+
var alias_detection_2 = require("./alias-detection");
|
|
414
|
+
Object.defineProperty(exports, "normalizeEmail", { enumerable: true, get: function () { return alias_detection_2.normalizeEmail; } });
|
|
415
|
+
Object.defineProperty(exports, "emailsMatch", { enumerable: true, get: function () { return alias_detection_2.emailsMatch; } });
|
|
395
416
|
/**
|
|
396
417
|
* Enhanced email provider detection with concurrent DNS for maximum performance.
|
|
397
418
|
* This function uses parallel MX/TXT lookups for 2x faster business domain detection.
|
|
398
419
|
*
|
|
420
|
+
* By default, returns a simplified response with only essential fields.
|
|
421
|
+
* Use the `extended` option to get full provider details including domains and alias configuration.
|
|
422
|
+
*
|
|
399
423
|
* @param email - The email address to analyze
|
|
400
424
|
* @param options - Configuration options for DNS detection
|
|
401
|
-
* @returns Promise resolving to EmailProviderResult with enhanced performance data
|
|
425
|
+
* @returns Promise resolving to SimplifiedEmailProviderResult (default) or EmailProviderResult (if extended) with enhanced performance data
|
|
402
426
|
*
|
|
403
427
|
* @example
|
|
404
428
|
* ```typescript
|
|
405
|
-
* //
|
|
429
|
+
* // Default: Simplified response with performance data
|
|
406
430
|
* const result = await getEmailProviderFast('user@mycompany.com', {
|
|
407
431
|
* enableParallel: true,
|
|
408
432
|
* collectDebugInfo: true
|
|
409
433
|
* });
|
|
434
|
+
* // Returns: { provider: { companyProvider, loginUrl, type }, email, loginUrl, detectionMethod, timing, confidence }
|
|
410
435
|
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
436
|
+
* // Extended response (includes domains, alias config, etc.)
|
|
437
|
+
* const extended = await getEmailProviderFast('user@mycompany.com', {
|
|
438
|
+
* enableParallel: true,
|
|
439
|
+
* extended: true
|
|
440
|
+
* });
|
|
441
|
+
* console.log(extended.provider?.companyProvider); // "Google Workspace"
|
|
442
|
+
* console.log(extended.detectionMethod); // "mx_record"
|
|
443
|
+
* console.log(extended.timing); // { mx: 120, txt: 95, total: 125 }
|
|
414
444
|
* ```
|
|
415
445
|
*/
|
|
416
446
|
async function getEmailProviderFast(email, options = {}) {
|
|
417
|
-
const { timeout = 5000, enableParallel = true, collectDebugInfo = false } = options;
|
|
447
|
+
const { timeout = 5000, enableParallel = true, collectDebugInfo = false, extended = false } = options;
|
|
418
448
|
try {
|
|
419
449
|
const parsed = validateAndParseEmailForLookup(email);
|
|
420
450
|
if (!parsed.ok) {
|
|
@@ -428,8 +458,9 @@ async function getEmailProviderFast(email, options = {}) {
|
|
|
428
458
|
const domain = parsed.domain;
|
|
429
459
|
const trimmedEmail = parsed.trimmedEmail;
|
|
430
460
|
// First try standard domain matching (fast path)
|
|
431
|
-
const syncResult = getEmailProviderSync(trimmedEmail);
|
|
461
|
+
const syncResult = getEmailProviderSync(trimmedEmail, { extended });
|
|
432
462
|
if (syncResult.provider) {
|
|
463
|
+
// Email is already normalized in getEmailProviderSync
|
|
433
464
|
return {
|
|
434
465
|
...syncResult,
|
|
435
466
|
detectionMethod: 'domain_match',
|
|
@@ -456,34 +487,62 @@ async function getEmailProviderFast(email, options = {}) {
|
|
|
456
487
|
enableParallel,
|
|
457
488
|
collectDebugInfo
|
|
458
489
|
});
|
|
490
|
+
// Normalize email using alias detection (even if no provider found)
|
|
491
|
+
// This ensures consistent email format regardless of provider detection result
|
|
492
|
+
let normalizedEmail = trimmedEmail;
|
|
493
|
+
try {
|
|
494
|
+
normalizedEmail = (0, alias_detection_1.normalizeEmail)(trimmedEmail);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
// If normalization fails, use original email
|
|
498
|
+
}
|
|
499
|
+
if (extended) {
|
|
500
|
+
const fastResult = {
|
|
501
|
+
provider: concurrentResult.provider,
|
|
502
|
+
email: normalizedEmail,
|
|
503
|
+
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
504
|
+
detectionMethod: concurrentResult.detectionMethod || 'mx_record',
|
|
505
|
+
timing: concurrentResult.timing,
|
|
506
|
+
confidence: concurrentResult.confidence,
|
|
507
|
+
debug: concurrentResult.debug,
|
|
508
|
+
error: !concurrentResult.provider && !concurrentResult.proxyService ? {
|
|
509
|
+
type: 'UNKNOWN_DOMAIN',
|
|
510
|
+
message: `No email provider found for domain: ${domain}`
|
|
511
|
+
} : undefined
|
|
512
|
+
};
|
|
513
|
+
if (concurrentResult.proxyService) {
|
|
514
|
+
fastResult.proxyService = concurrentResult.proxyService;
|
|
515
|
+
}
|
|
516
|
+
return fastResult;
|
|
517
|
+
}
|
|
518
|
+
// Default: simplified response
|
|
459
519
|
const fastResult = {
|
|
460
|
-
provider: concurrentResult.provider,
|
|
461
|
-
email:
|
|
462
|
-
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
520
|
+
provider: simplifyProvider(concurrentResult.provider),
|
|
521
|
+
email: normalizedEmail,
|
|
463
522
|
detectionMethod: concurrentResult.detectionMethod || 'mx_record',
|
|
464
523
|
timing: concurrentResult.timing,
|
|
465
524
|
confidence: concurrentResult.confidence,
|
|
466
525
|
debug: concurrentResult.debug,
|
|
467
|
-
error: !concurrentResult.provider
|
|
526
|
+
error: !concurrentResult.provider ? {
|
|
468
527
|
type: 'UNKNOWN_DOMAIN',
|
|
469
528
|
message: `No email provider found for domain: ${domain}`
|
|
470
529
|
} : undefined
|
|
471
530
|
};
|
|
472
|
-
if (concurrentResult.proxyService) {
|
|
473
|
-
fastResult.proxyService = concurrentResult.proxyService;
|
|
474
|
-
}
|
|
475
531
|
return fastResult;
|
|
476
532
|
}
|
|
477
533
|
catch (error) {
|
|
478
|
-
|
|
534
|
+
const errorResult = {
|
|
479
535
|
provider: null,
|
|
480
536
|
email,
|
|
481
|
-
loginUrl: null,
|
|
482
537
|
error: {
|
|
483
538
|
type: 'NETWORK_ERROR',
|
|
484
539
|
message: error instanceof Error ? error.message : 'DNS detection failed'
|
|
485
540
|
}
|
|
486
541
|
};
|
|
542
|
+
if (extended) {
|
|
543
|
+
errorResult.loginUrl = null;
|
|
544
|
+
}
|
|
545
|
+
return errorResult;
|
|
487
546
|
}
|
|
488
547
|
}
|
|
489
548
|
/**
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email validation constants based on RFC 5321
|
|
3
|
+
*
|
|
4
|
+
* These limits are defined in RFC 5321 Section 4.5.3.1:
|
|
5
|
+
* - Maximum total email length: 254 characters
|
|
6
|
+
* - Maximum local part length: 64 characters
|
|
7
|
+
* - Maximum domain length: 253 characters
|
|
8
|
+
*/
|
|
9
|
+
export declare const EmailLimits: {
|
|
10
|
+
/** Maximum total email address length (local + @ + domain) */
|
|
11
|
+
readonly MAX_EMAIL_LENGTH: 254;
|
|
12
|
+
/** Maximum local part length (before @) */
|
|
13
|
+
readonly MAX_LOCAL_PART_LENGTH: 64;
|
|
14
|
+
/** Maximum domain length (after @) */
|
|
15
|
+
readonly MAX_DOMAIN_LENGTH: 253;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Memory calculation constants
|
|
19
|
+
*/
|
|
20
|
+
export declare const MemoryConstants: {
|
|
21
|
+
/** Bytes per kilobyte */
|
|
22
|
+
readonly BYTES_PER_KB: 1024;
|
|
23
|
+
/** Kilobytes per megabyte */
|
|
24
|
+
readonly KB_PER_MB: 1024;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryConstants = exports.EmailLimits = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Email validation constants based on RFC 5321
|
|
6
|
+
*
|
|
7
|
+
* These limits are defined in RFC 5321 Section 4.5.3.1:
|
|
8
|
+
* - Maximum total email length: 254 characters
|
|
9
|
+
* - Maximum local part length: 64 characters
|
|
10
|
+
* - Maximum domain length: 253 characters
|
|
11
|
+
*/
|
|
12
|
+
exports.EmailLimits = {
|
|
13
|
+
/** Maximum total email address length (local + @ + domain) */
|
|
14
|
+
MAX_EMAIL_LENGTH: 254,
|
|
15
|
+
/** Maximum local part length (before @) */
|
|
16
|
+
MAX_LOCAL_PART_LENGTH: 64,
|
|
17
|
+
/** Maximum domain length (after @) */
|
|
18
|
+
MAX_DOMAIN_LENGTH: 253,
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Memory calculation constants
|
|
22
|
+
*/
|
|
23
|
+
exports.MemoryConstants = {
|
|
24
|
+
/** Bytes per kilobyte */
|
|
25
|
+
BYTES_PER_KB: 1024,
|
|
26
|
+
/** Kilobytes per megabyte */
|
|
27
|
+
KB_PER_MB: 1024,
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=constants.js.map
|