@mikkelscheike/email-provider-links 1.7.0 → 1.8.0
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 +106 -99
- package/dist/alias-detection.d.ts +30 -47
- package/dist/alias-detection.js +29 -104
- package/dist/api.d.ts +172 -0
- package/dist/api.js +439 -0
- package/dist/concurrent-dns.d.ts +130 -0
- package/dist/concurrent-dns.js +403 -0
- package/dist/index.d.ts +45 -353
- package/dist/index.js +65 -596
- package/dist/loader.d.ts +44 -0
- package/dist/loader.js +155 -0
- package/dist/schema.d.ts +66 -0
- package/dist/schema.js +73 -0
- package/dist/security/hash-verifier.js +2 -2
- package/package.json +2 -2
- package/providers/emailproviders.json +530 -479
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Provider Links API
|
|
3
|
+
*
|
|
4
|
+
* Simplified API with better error handling and performance improvements.
|
|
5
|
+
* Clean function names and enhanced error context.
|
|
6
|
+
*/
|
|
7
|
+
export interface EmailProvider {
|
|
8
|
+
companyProvider: string;
|
|
9
|
+
loginUrl: string;
|
|
10
|
+
domains: string[];
|
|
11
|
+
customDomainDetection?: {
|
|
12
|
+
mxPatterns?: string[];
|
|
13
|
+
txtPatterns?: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Enhanced result interface with rich error context
|
|
18
|
+
*/
|
|
19
|
+
export interface EmailProviderResult {
|
|
20
|
+
/** The detected email provider, or null if not found */
|
|
21
|
+
provider: EmailProvider | null;
|
|
22
|
+
/** The original email address that was analyzed */
|
|
23
|
+
email: string;
|
|
24
|
+
/** Direct URL to the email provider's login page, or null if unknown */
|
|
25
|
+
loginUrl: string | null;
|
|
26
|
+
/** Method used to detect the provider */
|
|
27
|
+
detectionMethod?: 'domain_match' | 'mx_record' | 'txt_record' | 'both' | 'proxy_detected';
|
|
28
|
+
/** If a proxy service was detected, which service (e.g., 'Cloudflare') */
|
|
29
|
+
proxyService?: string;
|
|
30
|
+
/** Error information if detection failed */
|
|
31
|
+
error?: {
|
|
32
|
+
type: 'INVALID_EMAIL' | 'DNS_TIMEOUT' | 'RATE_LIMITED' | 'UNKNOWN_DOMAIN' | 'NETWORK_ERROR';
|
|
33
|
+
message: string;
|
|
34
|
+
retryAfter?: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get email provider information for any email address.
|
|
39
|
+
*
|
|
40
|
+
* This is the primary function that handles all email types:
|
|
41
|
+
* - Consumer emails (gmail.com, yahoo.com, etc.)
|
|
42
|
+
* - Business domains (mycompany.com using Google Workspace, etc.)
|
|
43
|
+
* - Unknown providers (graceful fallback)
|
|
44
|
+
*
|
|
45
|
+
* @param email - The email address to analyze
|
|
46
|
+
* @param timeout - Optional timeout for DNS queries in milliseconds (default: 5000ms)
|
|
47
|
+
* @returns Promise resolving to EmailProviderResult with provider info and error context
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // Consumer email
|
|
52
|
+
* const gmail = await getEmailProvider('user@gmail.com');
|
|
53
|
+
* console.log(gmail.provider?.name); // "Gmail"
|
|
54
|
+
* console.log(gmail.loginUrl); // "https://mail.google.com/mail/"
|
|
55
|
+
*
|
|
56
|
+
* // Business domain
|
|
57
|
+
* const business = await getEmailProvider('user@mycompany.com');
|
|
58
|
+
* console.log(business.provider?.name); // "Google Workspace" (if detected)
|
|
59
|
+
* console.log(business.detectionMethod); // "mx_record"
|
|
60
|
+
*
|
|
61
|
+
* // Error handling
|
|
62
|
+
* const invalid = await getEmailProvider('invalid-email');
|
|
63
|
+
* console.log(invalid.error?.type); // "INVALID_EMAIL"
|
|
64
|
+
* console.log(invalid.error?.message); // "Invalid email format"
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function getEmailProvider(email: string, timeout?: number): Promise<EmailProviderResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Get email provider information synchronously (no DNS lookup).
|
|
70
|
+
*
|
|
71
|
+
* This function only checks predefined domains and returns immediately.
|
|
72
|
+
* Use this when you can't use async functions or don't want DNS lookups.
|
|
73
|
+
*
|
|
74
|
+
* @param email - The email address to analyze
|
|
75
|
+
* @returns EmailProviderResult with provider info (limited to known domains)
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // Works for known domains
|
|
80
|
+
* const gmail = getEmailProviderSync('user@gmail.com');
|
|
81
|
+
* console.log(gmail.provider?.name); // "Gmail"
|
|
82
|
+
*
|
|
83
|
+
* // Unknown domains return null
|
|
84
|
+
* const unknown = getEmailProviderSync('user@mycompany.com');
|
|
85
|
+
* console.log(unknown.provider); // null
|
|
86
|
+
* console.log(unknown.error?.type); // "UNKNOWN_DOMAIN"
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function getEmailProviderSync(email: string): EmailProviderResult;
|
|
90
|
+
/**
|
|
91
|
+
* Normalize an email address to its canonical form.
|
|
92
|
+
*
|
|
93
|
+
* This handles provider-specific aliasing rules:
|
|
94
|
+
* - Gmail: removes dots and plus addressing
|
|
95
|
+
* - Other providers: removes plus addressing only
|
|
96
|
+
*
|
|
97
|
+
* @param email - The email address to normalize
|
|
98
|
+
* @returns The canonical email address
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const canonical = normalizeEmail('U.S.E.R+work@GMAIL.COM');
|
|
103
|
+
* console.log(canonical); // 'user@gmail.com'
|
|
104
|
+
*
|
|
105
|
+
* const outlook = normalizeEmail('user+newsletter@outlook.com');
|
|
106
|
+
* console.log(outlook); // 'user@outlook.com'
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function normalizeEmail(email: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* Check if two email addresses are the same person (accounting for aliases).
|
|
112
|
+
*
|
|
113
|
+
* This normalizes both emails and compares their canonical forms.
|
|
114
|
+
* Useful for preventing duplicate accounts and matching login attempts.
|
|
115
|
+
*
|
|
116
|
+
* @param email1 - First email address
|
|
117
|
+
* @param email2 - Second email address
|
|
118
|
+
* @returns true if the emails represent the same person
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const match = emailsMatch('user@gmail.com', 'u.s.e.r+work@gmail.com');
|
|
123
|
+
* console.log(match); // true
|
|
124
|
+
*
|
|
125
|
+
* const different = emailsMatch('user@gmail.com', 'other@gmail.com');
|
|
126
|
+
* console.log(different); // false
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function emailsMatch(email1: string, email2: string): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Enhanced email provider detection with concurrent DNS for maximum performance.
|
|
132
|
+
* This function uses parallel MX/TXT lookups for 2x faster business domain detection.
|
|
133
|
+
*
|
|
134
|
+
* @param email - The email address to analyze
|
|
135
|
+
* @param options - Configuration options for DNS detection
|
|
136
|
+
* @returns Promise resolving to EmailProviderResult with enhanced performance data
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* // High-performance detection with concurrent DNS
|
|
141
|
+
* const result = await getEmailProviderFast('user@mycompany.com', {
|
|
142
|
+
* enableParallel: true,
|
|
143
|
+
* collectDebugInfo: true
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* console.log(result.provider?.companyProvider); // "Google Workspace"
|
|
147
|
+
* console.log(result.detectionMethod); // "mx_record"
|
|
148
|
+
* console.log(result.timing); // { mx: 120, txt: 95, total: 125 }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export declare function getEmailProviderFast(email: string, options?: {
|
|
152
|
+
timeout?: number;
|
|
153
|
+
enableParallel?: boolean;
|
|
154
|
+
collectDebugInfo?: boolean;
|
|
155
|
+
}): Promise<EmailProviderResult & {
|
|
156
|
+
timing?: {
|
|
157
|
+
mx: number;
|
|
158
|
+
txt: number;
|
|
159
|
+
total: number;
|
|
160
|
+
};
|
|
161
|
+
confidence?: number;
|
|
162
|
+
debug?: any;
|
|
163
|
+
}>;
|
|
164
|
+
/**
|
|
165
|
+
* Configuration constants
|
|
166
|
+
*/
|
|
167
|
+
export declare const Config: {
|
|
168
|
+
readonly DEFAULT_DNS_TIMEOUT: 5000;
|
|
169
|
+
readonly MAX_DNS_REQUESTS_PER_MINUTE: 10;
|
|
170
|
+
readonly SUPPORTED_PROVIDERS_COUNT: 93;
|
|
171
|
+
readonly SUPPORTED_DOMAINS_COUNT: 180;
|
|
172
|
+
};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Email Provider Links API
|
|
4
|
+
*
|
|
5
|
+
* Simplified API with better error handling and performance improvements.
|
|
6
|
+
* Clean function names and enhanced error context.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.Config = void 0;
|
|
10
|
+
exports.getEmailProvider = getEmailProvider;
|
|
11
|
+
exports.getEmailProviderSync = getEmailProviderSync;
|
|
12
|
+
exports.normalizeEmail = normalizeEmail;
|
|
13
|
+
exports.emailsMatch = emailsMatch;
|
|
14
|
+
exports.getEmailProviderFast = getEmailProviderFast;
|
|
15
|
+
const concurrent_dns_1 = require("./concurrent-dns");
|
|
16
|
+
const loader_1 = require("./loader");
|
|
17
|
+
/**
|
|
18
|
+
* Get email provider information for any email address.
|
|
19
|
+
*
|
|
20
|
+
* This is the primary function that handles all email types:
|
|
21
|
+
* - Consumer emails (gmail.com, yahoo.com, etc.)
|
|
22
|
+
* - Business domains (mycompany.com using Google Workspace, etc.)
|
|
23
|
+
* - Unknown providers (graceful fallback)
|
|
24
|
+
*
|
|
25
|
+
* @param email - The email address to analyze
|
|
26
|
+
* @param timeout - Optional timeout for DNS queries in milliseconds (default: 5000ms)
|
|
27
|
+
* @returns Promise resolving to EmailProviderResult with provider info and error context
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Consumer email
|
|
32
|
+
* const gmail = await getEmailProvider('user@gmail.com');
|
|
33
|
+
* console.log(gmail.provider?.name); // "Gmail"
|
|
34
|
+
* console.log(gmail.loginUrl); // "https://mail.google.com/mail/"
|
|
35
|
+
*
|
|
36
|
+
* // Business domain
|
|
37
|
+
* const business = await getEmailProvider('user@mycompany.com');
|
|
38
|
+
* console.log(business.provider?.name); // "Google Workspace" (if detected)
|
|
39
|
+
* console.log(business.detectionMethod); // "mx_record"
|
|
40
|
+
*
|
|
41
|
+
* // Error handling
|
|
42
|
+
* const invalid = await getEmailProvider('invalid-email');
|
|
43
|
+
* console.log(invalid.error?.type); // "INVALID_EMAIL"
|
|
44
|
+
* console.log(invalid.error?.message); // "Invalid email format"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
async function getEmailProvider(email, timeout) {
|
|
48
|
+
try {
|
|
49
|
+
// Input validation
|
|
50
|
+
if (!email || typeof email !== 'string') {
|
|
51
|
+
return {
|
|
52
|
+
provider: null,
|
|
53
|
+
email: email || '',
|
|
54
|
+
loginUrl: null,
|
|
55
|
+
error: {
|
|
56
|
+
type: 'INVALID_EMAIL',
|
|
57
|
+
message: 'Email address is required and must be a string'
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Basic email format validation
|
|
62
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
63
|
+
if (!emailRegex.test(email)) {
|
|
64
|
+
return {
|
|
65
|
+
provider: null,
|
|
66
|
+
email,
|
|
67
|
+
loginUrl: null,
|
|
68
|
+
error: {
|
|
69
|
+
type: 'INVALID_EMAIL',
|
|
70
|
+
message: 'Invalid email format'
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const domain = email.split('@')[1]?.toLowerCase();
|
|
75
|
+
if (!domain) {
|
|
76
|
+
return {
|
|
77
|
+
provider: null,
|
|
78
|
+
email,
|
|
79
|
+
loginUrl: null,
|
|
80
|
+
error: {
|
|
81
|
+
type: 'INVALID_EMAIL',
|
|
82
|
+
message: 'Invalid email format - missing domain'
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// First try synchronous domain matching
|
|
87
|
+
const syncResult = getEmailProviderSync(email);
|
|
88
|
+
if (syncResult.provider) {
|
|
89
|
+
return {
|
|
90
|
+
...syncResult,
|
|
91
|
+
detectionMethod: 'domain_match'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Fall back to DNS detection for business domains
|
|
95
|
+
const { providers } = (0, loader_1.loadProviders)();
|
|
96
|
+
const concurrentResult = await (0, concurrent_dns_1.detectProviderConcurrent)(domain, providers, {
|
|
97
|
+
timeout: timeout || 5000,
|
|
98
|
+
enableParallel: true,
|
|
99
|
+
collectDebugInfo: false
|
|
100
|
+
});
|
|
101
|
+
const result = {
|
|
102
|
+
provider: concurrentResult.provider,
|
|
103
|
+
email,
|
|
104
|
+
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
105
|
+
detectionMethod: concurrentResult.detectionMethod || undefined,
|
|
106
|
+
proxyService: concurrentResult.proxyService
|
|
107
|
+
};
|
|
108
|
+
// Add error context for null results
|
|
109
|
+
if (!result.provider && !result.proxyService) {
|
|
110
|
+
result.error = {
|
|
111
|
+
type: 'UNKNOWN_DOMAIN',
|
|
112
|
+
message: `No email provider found for domain: ${domain}`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Enhanced error handling
|
|
119
|
+
if (error.message?.includes('Rate limit exceeded')) {
|
|
120
|
+
const retryMatch = error.message.match(/Try again in (\d+) seconds/);
|
|
121
|
+
const retryAfter = retryMatch ? parseInt(retryMatch[1]) : undefined;
|
|
122
|
+
return {
|
|
123
|
+
provider: null,
|
|
124
|
+
email,
|
|
125
|
+
loginUrl: null,
|
|
126
|
+
error: {
|
|
127
|
+
type: 'RATE_LIMITED',
|
|
128
|
+
message: 'DNS query rate limit exceeded',
|
|
129
|
+
retryAfter
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (error.message?.includes('timeout')) {
|
|
134
|
+
return {
|
|
135
|
+
provider: null,
|
|
136
|
+
email,
|
|
137
|
+
loginUrl: null,
|
|
138
|
+
error: {
|
|
139
|
+
type: 'DNS_TIMEOUT',
|
|
140
|
+
message: `DNS lookup timed out after ${timeout || 5000}ms`
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
provider: null,
|
|
146
|
+
email,
|
|
147
|
+
loginUrl: null,
|
|
148
|
+
error: {
|
|
149
|
+
type: 'NETWORK_ERROR',
|
|
150
|
+
message: error.message || 'Unknown network error'
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get email provider information synchronously (no DNS lookup).
|
|
157
|
+
*
|
|
158
|
+
* This function only checks predefined domains and returns immediately.
|
|
159
|
+
* Use this when you can't use async functions or don't want DNS lookups.
|
|
160
|
+
*
|
|
161
|
+
* @param email - The email address to analyze
|
|
162
|
+
* @returns EmailProviderResult with provider info (limited to known domains)
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* // Works for known domains
|
|
167
|
+
* const gmail = getEmailProviderSync('user@gmail.com');
|
|
168
|
+
* console.log(gmail.provider?.name); // "Gmail"
|
|
169
|
+
*
|
|
170
|
+
* // Unknown domains return null
|
|
171
|
+
* const unknown = getEmailProviderSync('user@mycompany.com');
|
|
172
|
+
* console.log(unknown.provider); // null
|
|
173
|
+
* console.log(unknown.error?.type); // "UNKNOWN_DOMAIN"
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
function getEmailProviderSync(email) {
|
|
177
|
+
try {
|
|
178
|
+
// Input validation
|
|
179
|
+
if (!email || typeof email !== 'string') {
|
|
180
|
+
return {
|
|
181
|
+
provider: null,
|
|
182
|
+
email: email || '',
|
|
183
|
+
loginUrl: null,
|
|
184
|
+
error: {
|
|
185
|
+
type: 'INVALID_EMAIL',
|
|
186
|
+
message: 'Email address is required and must be a string'
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Basic email format validation
|
|
191
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
192
|
+
if (!emailRegex.test(email)) {
|
|
193
|
+
return {
|
|
194
|
+
provider: null,
|
|
195
|
+
email,
|
|
196
|
+
loginUrl: null,
|
|
197
|
+
error: {
|
|
198
|
+
type: 'INVALID_EMAIL',
|
|
199
|
+
message: 'Invalid email format'
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Pure synchronous domain matching
|
|
204
|
+
const domain = email.split('@')[1]?.toLowerCase();
|
|
205
|
+
if (!domain) {
|
|
206
|
+
return {
|
|
207
|
+
provider: null,
|
|
208
|
+
email,
|
|
209
|
+
loginUrl: null,
|
|
210
|
+
error: {
|
|
211
|
+
type: 'INVALID_EMAIL',
|
|
212
|
+
message: 'Invalid email format - missing domain'
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// Load providers and find matching domain
|
|
217
|
+
const { providers } = (0, loader_1.loadProviders)();
|
|
218
|
+
const provider = providers.find(p => p.domains?.some(d => d.toLowerCase() === domain));
|
|
219
|
+
const result = {
|
|
220
|
+
provider: provider || null,
|
|
221
|
+
email,
|
|
222
|
+
loginUrl: provider?.loginUrl || null,
|
|
223
|
+
detectionMethod: 'domain_match'
|
|
224
|
+
};
|
|
225
|
+
// Add error context for null results
|
|
226
|
+
if (!result.provider) {
|
|
227
|
+
result.error = {
|
|
228
|
+
type: 'UNKNOWN_DOMAIN',
|
|
229
|
+
message: `No email provider found for domain: ${domain} (sync mode - business domains not supported)`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
provider: null,
|
|
237
|
+
email,
|
|
238
|
+
loginUrl: null,
|
|
239
|
+
error: {
|
|
240
|
+
type: 'INVALID_EMAIL',
|
|
241
|
+
message: error.message || 'Invalid email address'
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Normalize an email address to its canonical form.
|
|
248
|
+
*
|
|
249
|
+
* This handles provider-specific aliasing rules:
|
|
250
|
+
* - Gmail: removes dots and plus addressing
|
|
251
|
+
* - Other providers: removes plus addressing only
|
|
252
|
+
*
|
|
253
|
+
* @param email - The email address to normalize
|
|
254
|
+
* @returns The canonical email address
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```typescript
|
|
258
|
+
* const canonical = normalizeEmail('U.S.E.R+work@GMAIL.COM');
|
|
259
|
+
* console.log(canonical); // 'user@gmail.com'
|
|
260
|
+
*
|
|
261
|
+
* const outlook = normalizeEmail('user+newsletter@outlook.com');
|
|
262
|
+
* console.log(outlook); // 'user@outlook.com'
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
function normalizeEmail(email) {
|
|
266
|
+
if (!email || typeof email !== 'string') {
|
|
267
|
+
return email;
|
|
268
|
+
}
|
|
269
|
+
// Convert to lowercase
|
|
270
|
+
const lowercaseEmail = email.toLowerCase().trim();
|
|
271
|
+
// Split email into local and domain parts
|
|
272
|
+
const atIndex = lowercaseEmail.lastIndexOf('@');
|
|
273
|
+
if (atIndex === -1) {
|
|
274
|
+
return lowercaseEmail;
|
|
275
|
+
}
|
|
276
|
+
let localPart = lowercaseEmail.slice(0, atIndex);
|
|
277
|
+
const domainPart = lowercaseEmail.slice(atIndex + 1);
|
|
278
|
+
// Gmail-specific rules: remove dots and plus addressing
|
|
279
|
+
if (domainPart === 'gmail.com' || domainPart === 'googlemail.com') {
|
|
280
|
+
// Remove all dots from local part
|
|
281
|
+
localPart = localPart.replace(/\./g, '');
|
|
282
|
+
// Remove plus addressing (everything after +)
|
|
283
|
+
const plusIndex = localPart.indexOf('+');
|
|
284
|
+
if (plusIndex !== -1) {
|
|
285
|
+
localPart = localPart.slice(0, plusIndex);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// For other providers, only remove plus addressing
|
|
290
|
+
const plusIndex = localPart.indexOf('+');
|
|
291
|
+
if (plusIndex !== -1) {
|
|
292
|
+
localPart = localPart.slice(0, plusIndex);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return `${localPart}@${domainPart}`;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Check if two email addresses are the same person (accounting for aliases).
|
|
299
|
+
*
|
|
300
|
+
* This normalizes both emails and compares their canonical forms.
|
|
301
|
+
* Useful for preventing duplicate accounts and matching login attempts.
|
|
302
|
+
*
|
|
303
|
+
* @param email1 - First email address
|
|
304
|
+
* @param email2 - Second email address
|
|
305
|
+
* @returns true if the emails represent the same person
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const match = emailsMatch('user@gmail.com', 'u.s.e.r+work@gmail.com');
|
|
310
|
+
* console.log(match); // true
|
|
311
|
+
*
|
|
312
|
+
* const different = emailsMatch('user@gmail.com', 'other@gmail.com');
|
|
313
|
+
* console.log(different); // false
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
function emailsMatch(email1, email2) {
|
|
317
|
+
if (!email1 || !email2 || typeof email1 !== 'string' || typeof email2 !== 'string') {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
// Normalize both emails and compare
|
|
321
|
+
const normalized1 = normalizeEmail(email1);
|
|
322
|
+
const normalized2 = normalizeEmail(email2);
|
|
323
|
+
return normalized1 === normalized2;
|
|
324
|
+
}
|
|
325
|
+
// Note: EmailProvider type is defined above
|
|
326
|
+
/**
|
|
327
|
+
* Enhanced email provider detection with concurrent DNS for maximum performance.
|
|
328
|
+
* This function uses parallel MX/TXT lookups for 2x faster business domain detection.
|
|
329
|
+
*
|
|
330
|
+
* @param email - The email address to analyze
|
|
331
|
+
* @param options - Configuration options for DNS detection
|
|
332
|
+
* @returns Promise resolving to EmailProviderResult with enhanced performance data
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* // High-performance detection with concurrent DNS
|
|
337
|
+
* const result = await getEmailProviderFast('user@mycompany.com', {
|
|
338
|
+
* enableParallel: true,
|
|
339
|
+
* collectDebugInfo: true
|
|
340
|
+
* });
|
|
341
|
+
*
|
|
342
|
+
* console.log(result.provider?.companyProvider); // "Google Workspace"
|
|
343
|
+
* console.log(result.detectionMethod); // "mx_record"
|
|
344
|
+
* console.log(result.timing); // { mx: 120, txt: 95, total: 125 }
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
async function getEmailProviderFast(email, options = {}) {
|
|
348
|
+
const { timeout = 5000, enableParallel = true, collectDebugInfo = false } = options;
|
|
349
|
+
try {
|
|
350
|
+
// Input validation
|
|
351
|
+
if (!email || typeof email !== 'string') {
|
|
352
|
+
return {
|
|
353
|
+
provider: null,
|
|
354
|
+
email: email || '',
|
|
355
|
+
loginUrl: null,
|
|
356
|
+
error: {
|
|
357
|
+
type: 'INVALID_EMAIL',
|
|
358
|
+
message: 'Email address is required and must be a string'
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
// Basic email format validation
|
|
363
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
364
|
+
if (!emailRegex.test(email)) {
|
|
365
|
+
return {
|
|
366
|
+
provider: null,
|
|
367
|
+
email,
|
|
368
|
+
loginUrl: null,
|
|
369
|
+
error: {
|
|
370
|
+
type: 'INVALID_EMAIL',
|
|
371
|
+
message: 'Invalid email format'
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const domain = email.split('@')[1]?.toLowerCase();
|
|
376
|
+
if (!domain) {
|
|
377
|
+
return {
|
|
378
|
+
provider: null,
|
|
379
|
+
email,
|
|
380
|
+
loginUrl: null,
|
|
381
|
+
error: {
|
|
382
|
+
type: 'INVALID_EMAIL',
|
|
383
|
+
message: 'Invalid email format - missing domain'
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
// First try standard domain matching (fast path)
|
|
388
|
+
const syncResult = getEmailProviderSync(email);
|
|
389
|
+
if (syncResult.provider) {
|
|
390
|
+
return {
|
|
391
|
+
...syncResult,
|
|
392
|
+
detectionMethod: 'domain_match',
|
|
393
|
+
timing: { mx: 0, txt: 0, total: 0 },
|
|
394
|
+
confidence: 1.0
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// Fall back to concurrent DNS detection for business domains
|
|
398
|
+
const { providers } = (0, loader_1.loadProviders)();
|
|
399
|
+
const concurrentResult = await (0, concurrent_dns_1.detectProviderConcurrent)(domain, providers, {
|
|
400
|
+
timeout,
|
|
401
|
+
enableParallel,
|
|
402
|
+
collectDebugInfo
|
|
403
|
+
});
|
|
404
|
+
return {
|
|
405
|
+
provider: concurrentResult.provider,
|
|
406
|
+
email,
|
|
407
|
+
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
408
|
+
detectionMethod: concurrentResult.detectionMethod || undefined,
|
|
409
|
+
proxyService: concurrentResult.proxyService,
|
|
410
|
+
timing: concurrentResult.timing,
|
|
411
|
+
confidence: concurrentResult.confidence,
|
|
412
|
+
debug: concurrentResult.debug,
|
|
413
|
+
error: !concurrentResult.provider && !concurrentResult.proxyService ? {
|
|
414
|
+
type: 'UNKNOWN_DOMAIN',
|
|
415
|
+
message: `No email provider found for domain: ${domain}`
|
|
416
|
+
} : undefined
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
return {
|
|
421
|
+
provider: null,
|
|
422
|
+
email,
|
|
423
|
+
loginUrl: null,
|
|
424
|
+
error: {
|
|
425
|
+
type: 'NETWORK_ERROR',
|
|
426
|
+
message: error.message || 'DNS detection failed'
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Configuration constants
|
|
433
|
+
*/
|
|
434
|
+
exports.Config = {
|
|
435
|
+
DEFAULT_DNS_TIMEOUT: 5000,
|
|
436
|
+
MAX_DNS_REQUESTS_PER_MINUTE: 10,
|
|
437
|
+
SUPPORTED_PROVIDERS_COUNT: 93,
|
|
438
|
+
SUPPORTED_DOMAINS_COUNT: 180
|
|
439
|
+
};
|