@mikkelscheike/email-provider-links 4.0.10 → 5.0.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 +23 -5
- package/dist/alias-detection.js +4 -3
- package/dist/api.d.ts +3 -3
- package/dist/api.js +98 -122
- package/dist/concurrent-dns.d.ts +7 -2
- package/dist/concurrent-dns.js +10 -3
- package/dist/hash-verifier.d.ts +7 -2
- package/dist/hash-verifier.js +13 -4
- package/dist/index.d.ts +15 -20
- package/dist/index.js +35 -34
- package/dist/loader.js +5 -47
- package/dist/provider-loader.d.ts +12 -2
- package/dist/provider-loader.js +6 -40
- package/dist/provider-store.d.ts +9 -0
- package/dist/provider-store.js +49 -0
- package/dist/url-validator.d.ts +7 -2
- package/dist/url-validator.js +19 -3
- package/package.json +2 -2
- package/providers/emailproviders.json +103 -24
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@mikkelscheike/email-provider-links)
|
|
4
4
|
|
|
5
|
-
> **Generate direct login links for any email address across
|
|
5
|
+
> **Generate direct login links for any email address across 130+ providers (Gmail, Outlook, Yahoo, etc.) to streamline user authentication flows.**
|
|
6
6
|
|
|
7
|
-
A robust TypeScript library providing direct links to **
|
|
7
|
+
A robust TypeScript library providing direct links to **130 email providers** (218 domains) with **concurrent DNS resolution**, **optimized performance**, **comprehensive email validation**, and advanced security features for login and password reset flows.
|
|
8
8
|
|
|
9
9
|
## 🚀 Try it out
|
|
10
10
|
|
|
@@ -26,8 +26,8 @@ A robust TypeScript library providing direct links to **93 email providers** (18
|
|
|
26
26
|
## ✨ Core Features
|
|
27
27
|
|
|
28
28
|
- 🚀 **Fast & Lightweight**: Zero dependencies, ultra-low memory (0.10MB initial, 0.00004MB per 1000 ops), small footprint (~39.5KB compressed)
|
|
29
|
-
- 📧 **
|
|
30
|
-
- 🌐 **
|
|
29
|
+
- 📧 **130 Email Providers**: Gmail, Outlook, Yahoo, ProtonMail, iCloud, and many more
|
|
30
|
+
- 🌐 **218 Domains Supported**: Comprehensive international coverage
|
|
31
31
|
- 🌍 **Full IDN Support**: International domain names with RFC compliance and Punycode
|
|
32
32
|
- ✅ **Advanced Email Validation**: International email validation with detailed error reporting
|
|
33
33
|
- 🏢 **Business Domain Detection**: DNS-based detection for custom domains (Google Workspace, Microsoft 365, etc.)
|
|
@@ -66,7 +66,7 @@ Fully compatible with the latest Node.js 24.x and 25.x! The library is tested on
|
|
|
66
66
|
|
|
67
67
|
## Supported Providers
|
|
68
68
|
|
|
69
|
-
**📊 Current Coverage:
|
|
69
|
+
**📊 Current Coverage: 130 providers supporting 218 domains**
|
|
70
70
|
|
|
71
71
|
**Consumer Email Providers:**
|
|
72
72
|
- **Gmail** (2 domains): gmail.com, googlemail.com
|
|
@@ -102,6 +102,10 @@ Fully compatible with the latest Node.js 24.x and 25.x! The library is tested on
|
|
|
102
102
|
#### `getEmailProvider(email, timeout?)`
|
|
103
103
|
**Recommended** - Complete provider detection with business domain support.
|
|
104
104
|
|
|
105
|
+
Error notes:
|
|
106
|
+
- `INVALID_EMAIL` is returned for common malformed inputs (e.g. missing `@`, missing TLD).
|
|
107
|
+
- `IDN_VALIDATION_ERROR` is reserved for true encoding issues.
|
|
108
|
+
|
|
105
109
|
```typescript
|
|
106
110
|
// Known providers (instant response)
|
|
107
111
|
const result1 = await getEmailProvider('user@gmail.com');
|
|
@@ -286,6 +290,20 @@ npm run benchmark:dns
|
|
|
286
290
|
# and can be modified for custom performance testing
|
|
287
291
|
```
|
|
288
292
|
|
|
293
|
+
### Live DNS verification (optional)
|
|
294
|
+
|
|
295
|
+
There is an optional test suite that performs real DNS lookups for all domains in `providers/emailproviders.json`:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
RUN_LIVE_DNS=1 npm test -- __tests__/provider-live-dns.test.ts
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Optional strict mode (also validates configured MX/TXT patterns):
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
RUN_LIVE_DNS=1 RUN_LIVE_DNS_STRICT=1 npm test -- __tests__/provider-live-dns.test.ts
|
|
305
|
+
```
|
|
306
|
+
|
|
289
307
|
## Contributing
|
|
290
308
|
|
|
291
309
|
We welcome contributions! See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines on adding new email providers.
|
package/dist/alias-detection.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.detectEmailAlias = detectEmailAlias;
|
|
|
47
47
|
exports.normalizeEmail = normalizeEmail;
|
|
48
48
|
exports.emailsMatch = emailsMatch;
|
|
49
49
|
const loader_1 = require("./loader");
|
|
50
|
+
const idn_1 = require("./idn");
|
|
50
51
|
/**
|
|
51
52
|
* Validates email format
|
|
52
53
|
*/
|
|
@@ -79,14 +80,14 @@ function isValidEmail(email) {
|
|
|
79
80
|
* ```
|
|
80
81
|
*/
|
|
81
82
|
function detectEmailAlias(email) {
|
|
82
|
-
|
|
83
|
+
const originalEmail = email.trim();
|
|
84
|
+
if (!isValidEmail(originalEmail)) {
|
|
83
85
|
throw new Error('Invalid email format');
|
|
84
86
|
}
|
|
85
|
-
const originalEmail = email.trim();
|
|
86
87
|
// Split normally, lowering case both for username and domain by default
|
|
87
88
|
const emailParts = originalEmail.toLowerCase().split('@');
|
|
88
89
|
const username = emailParts[0];
|
|
89
|
-
const domain = emailParts[1]; // domain is always case-insensitive per RFC 5321
|
|
90
|
+
const domain = (0, idn_1.domainToPunycode)(emailParts[1] || ''); // domain is always case-insensitive per RFC 5321
|
|
90
91
|
if (!username || !domain) {
|
|
91
92
|
throw new Error('Invalid email format - missing username or domain');
|
|
92
93
|
}
|
package/dist/api.d.ts
CHANGED
|
@@ -176,7 +176,7 @@ export declare function getEmailProviderFast(email: string, options?: {
|
|
|
176
176
|
total: number;
|
|
177
177
|
};
|
|
178
178
|
confidence?: number;
|
|
179
|
-
debug?:
|
|
179
|
+
debug?: unknown;
|
|
180
180
|
}>;
|
|
181
181
|
/**
|
|
182
182
|
* Configuration constants
|
|
@@ -184,7 +184,7 @@ export declare function getEmailProviderFast(email: string, options?: {
|
|
|
184
184
|
export declare const Config: {
|
|
185
185
|
readonly DEFAULT_DNS_TIMEOUT: 5000;
|
|
186
186
|
readonly MAX_DNS_REQUESTS_PER_MINUTE: 10;
|
|
187
|
-
readonly SUPPORTED_PROVIDERS_COUNT:
|
|
188
|
-
readonly SUPPORTED_DOMAINS_COUNT:
|
|
187
|
+
readonly SUPPORTED_PROVIDERS_COUNT: 130;
|
|
188
|
+
readonly SUPPORTED_DOMAINS_COUNT: 218;
|
|
189
189
|
};
|
|
190
190
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.js
CHANGED
|
@@ -14,6 +14,74 @@ exports.emailsMatch = emailsMatch;
|
|
|
14
14
|
exports.getEmailProviderFast = getEmailProviderFast;
|
|
15
15
|
const concurrent_dns_1 = require("./concurrent-dns");
|
|
16
16
|
const provider_loader_1 = require("./provider-loader");
|
|
17
|
+
const idn_1 = require("./idn");
|
|
18
|
+
let cachedProvidersRef = null;
|
|
19
|
+
let cachedDomainMap = null;
|
|
20
|
+
function getDomainMapFromProviders(providers) {
|
|
21
|
+
if (cachedProvidersRef === providers && cachedDomainMap) {
|
|
22
|
+
return cachedDomainMap;
|
|
23
|
+
}
|
|
24
|
+
const domainMap = new Map();
|
|
25
|
+
for (const loadedProvider of providers) {
|
|
26
|
+
for (const domain of loadedProvider.domains) {
|
|
27
|
+
domainMap.set(domain.toLowerCase(), loadedProvider);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
cachedProvidersRef = providers;
|
|
31
|
+
cachedDomainMap = domainMap;
|
|
32
|
+
return domainMap;
|
|
33
|
+
}
|
|
34
|
+
function validateAndParseEmailForLookup(email) {
|
|
35
|
+
if (!email || typeof email !== 'string') {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
email: email || '',
|
|
39
|
+
error: {
|
|
40
|
+
type: 'INVALID_EMAIL',
|
|
41
|
+
message: 'Email address is required and must be a string'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const trimmedEmail = email.trim();
|
|
46
|
+
// Strict validation: treat any IDN validation failure as invalid input.
|
|
47
|
+
// Only surface IDN_VALIDATION_ERROR for true encoding issues.
|
|
48
|
+
const idnError = (0, idn_1.validateInternationalEmail)(trimmedEmail);
|
|
49
|
+
if (idnError) {
|
|
50
|
+
if (idnError.code === idn_1.IDNValidationError.INVALID_ENCODING) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
email: trimmedEmail,
|
|
54
|
+
error: {
|
|
55
|
+
type: 'IDN_VALIDATION_ERROR',
|
|
56
|
+
message: idnError.message,
|
|
57
|
+
idnError: idnError.code
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
email: trimmedEmail,
|
|
64
|
+
error: {
|
|
65
|
+
type: 'INVALID_EMAIL',
|
|
66
|
+
message: 'Invalid email format'
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const atIndex = trimmedEmail.lastIndexOf('@');
|
|
71
|
+
if (atIndex === -1) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
email: trimmedEmail,
|
|
75
|
+
error: {
|
|
76
|
+
type: 'INVALID_EMAIL',
|
|
77
|
+
message: 'Invalid email format'
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const domainRaw = trimmedEmail.slice(atIndex + 1).toLowerCase();
|
|
82
|
+
const domain = (0, idn_1.domainToPunycode)(domainRaw);
|
|
83
|
+
return { ok: true, trimmedEmail, domain };
|
|
84
|
+
}
|
|
17
85
|
/**
|
|
18
86
|
* Get email provider information for any email address.
|
|
19
87
|
*
|
|
@@ -46,43 +114,16 @@ const provider_loader_1 = require("./provider-loader");
|
|
|
46
114
|
*/
|
|
47
115
|
async function getEmailProvider(email, timeout) {
|
|
48
116
|
try {
|
|
49
|
-
|
|
50
|
-
if (!
|
|
117
|
+
const parsed = validateAndParseEmailForLookup(email);
|
|
118
|
+
if (!parsed.ok) {
|
|
51
119
|
return {
|
|
52
120
|
provider: null,
|
|
53
|
-
email: email
|
|
121
|
+
email: parsed.email,
|
|
54
122
|
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
|
-
}
|
|
123
|
+
error: parsed.error
|
|
84
124
|
};
|
|
85
125
|
}
|
|
126
|
+
const domain = parsed.domain;
|
|
86
127
|
// First try synchronous domain matching
|
|
87
128
|
const syncResult = getEmailProviderSync(email);
|
|
88
129
|
if (syncResult.provider) {
|
|
@@ -130,9 +171,9 @@ async function getEmailProvider(email, timeout) {
|
|
|
130
171
|
}
|
|
131
172
|
catch (error) {
|
|
132
173
|
// Enhanced error handling
|
|
133
|
-
if (error.message
|
|
174
|
+
if (error instanceof Error && error.message.includes('Rate limit exceeded')) {
|
|
134
175
|
const retryMatch = error.message.match(/Try again in (\d+) seconds/);
|
|
135
|
-
const retryAfter = retryMatch ? parseInt(retryMatch[1], 10) : undefined;
|
|
176
|
+
const retryAfter = retryMatch?.[1] ? parseInt(retryMatch[1], 10) : undefined;
|
|
136
177
|
return {
|
|
137
178
|
provider: null,
|
|
138
179
|
email,
|
|
@@ -144,7 +185,7 @@ async function getEmailProvider(email, timeout) {
|
|
|
144
185
|
}
|
|
145
186
|
};
|
|
146
187
|
}
|
|
147
|
-
if (error.message
|
|
188
|
+
if (error instanceof Error && error.message.includes('timeout')) {
|
|
148
189
|
return {
|
|
149
190
|
provider: null,
|
|
150
191
|
email,
|
|
@@ -161,7 +202,7 @@ async function getEmailProvider(email, timeout) {
|
|
|
161
202
|
loginUrl: null,
|
|
162
203
|
error: {
|
|
163
204
|
type: 'NETWORK_ERROR',
|
|
164
|
-
message: error.message
|
|
205
|
+
message: error instanceof Error ? error.message : 'Unknown network error'
|
|
165
206
|
}
|
|
166
207
|
};
|
|
167
208
|
}
|
|
@@ -189,44 +230,16 @@ async function getEmailProvider(email, timeout) {
|
|
|
189
230
|
*/
|
|
190
231
|
function getEmailProviderSync(email) {
|
|
191
232
|
try {
|
|
192
|
-
|
|
193
|
-
if (!
|
|
194
|
-
return {
|
|
195
|
-
provider: null,
|
|
196
|
-
email: email || '',
|
|
197
|
-
loginUrl: null,
|
|
198
|
-
error: {
|
|
199
|
-
type: 'INVALID_EMAIL',
|
|
200
|
-
message: 'Email address is required and must be a string'
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
// Basic email format validation
|
|
205
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
206
|
-
if (!emailRegex.test(email)) {
|
|
233
|
+
const parsed = validateAndParseEmailForLookup(email);
|
|
234
|
+
if (!parsed.ok) {
|
|
207
235
|
return {
|
|
208
236
|
provider: null,
|
|
209
|
-
email,
|
|
237
|
+
email: parsed.email,
|
|
210
238
|
loginUrl: null,
|
|
211
|
-
error:
|
|
212
|
-
type: 'INVALID_EMAIL',
|
|
213
|
-
message: 'Invalid email format'
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
// Pure synchronous domain matching
|
|
218
|
-
const domain = email.split('@')[1]?.toLowerCase();
|
|
219
|
-
if (!domain) {
|
|
220
|
-
return {
|
|
221
|
-
provider: null,
|
|
222
|
-
email,
|
|
223
|
-
loginUrl: null,
|
|
224
|
-
error: {
|
|
225
|
-
type: 'INVALID_EMAIL',
|
|
226
|
-
message: 'Invalid email format - missing domain'
|
|
227
|
-
}
|
|
239
|
+
error: parsed.error
|
|
228
240
|
};
|
|
229
241
|
}
|
|
242
|
+
const domain = parsed.domain;
|
|
230
243
|
// Load providers with verification
|
|
231
244
|
let provider = null;
|
|
232
245
|
try {
|
|
@@ -246,13 +259,7 @@ function getEmailProviderSync(email) {
|
|
|
246
259
|
}
|
|
247
260
|
};
|
|
248
261
|
}
|
|
249
|
-
const domainMap =
|
|
250
|
-
// Build domain map from loaded providers
|
|
251
|
-
for (const loadedProvider of result.providers) {
|
|
252
|
-
for (const domain of loadedProvider.domains) {
|
|
253
|
-
domainMap.set(domain.toLowerCase(), loadedProvider);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
262
|
+
const domainMap = getDomainMapFromProviders(result.providers);
|
|
256
263
|
provider = domainMap.get(domain) || null;
|
|
257
264
|
}
|
|
258
265
|
catch (error) {
|
|
@@ -291,7 +298,7 @@ function getEmailProviderSync(email) {
|
|
|
291
298
|
loginUrl: null,
|
|
292
299
|
error: {
|
|
293
300
|
type: 'INVALID_EMAIL',
|
|
294
|
-
message: error.message
|
|
301
|
+
message: error instanceof Error ? error.message : 'Invalid email address'
|
|
295
302
|
}
|
|
296
303
|
};
|
|
297
304
|
}
|
|
@@ -327,7 +334,7 @@ function normalizeEmail(email) {
|
|
|
327
334
|
return lowercaseEmail;
|
|
328
335
|
}
|
|
329
336
|
let localPart = lowercaseEmail.slice(0, atIndex);
|
|
330
|
-
const domainPart = lowercaseEmail.slice(atIndex + 1);
|
|
337
|
+
const domainPart = (0, idn_1.domainToPunycode)(lowercaseEmail.slice(atIndex + 1));
|
|
331
338
|
// Use providers for domain lookup
|
|
332
339
|
let provider = null;
|
|
333
340
|
try {
|
|
@@ -335,12 +342,7 @@ function normalizeEmail(email) {
|
|
|
335
342
|
if (!result.success) {
|
|
336
343
|
return lowercaseEmail; // Return as-is if providers can't be loaded
|
|
337
344
|
}
|
|
338
|
-
const domainMap =
|
|
339
|
-
for (const loadedProvider of result.providers) {
|
|
340
|
-
for (const domain of loadedProvider.domains) {
|
|
341
|
-
domainMap.set(domain.toLowerCase(), loadedProvider);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
345
|
+
const domainMap = getDomainMapFromProviders(result.providers);
|
|
344
346
|
provider = domainMap.get(domainPart) || null;
|
|
345
347
|
}
|
|
346
348
|
catch (error) {
|
|
@@ -414,45 +416,19 @@ function emailsMatch(email1, email2) {
|
|
|
414
416
|
async function getEmailProviderFast(email, options = {}) {
|
|
415
417
|
const { timeout = 5000, enableParallel = true, collectDebugInfo = false } = options;
|
|
416
418
|
try {
|
|
417
|
-
|
|
418
|
-
if (!
|
|
419
|
-
return {
|
|
420
|
-
provider: null,
|
|
421
|
-
email: email || '',
|
|
422
|
-
loginUrl: null,
|
|
423
|
-
error: {
|
|
424
|
-
type: 'INVALID_EMAIL',
|
|
425
|
-
message: 'Email address is required and must be a string'
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
// Basic email format validation
|
|
430
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
431
|
-
if (!emailRegex.test(email)) {
|
|
432
|
-
return {
|
|
433
|
-
provider: null,
|
|
434
|
-
email,
|
|
435
|
-
loginUrl: null,
|
|
436
|
-
error: {
|
|
437
|
-
type: 'INVALID_EMAIL',
|
|
438
|
-
message: 'Invalid email format'
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
const domain = email.split('@')[1]?.toLowerCase();
|
|
443
|
-
if (!domain) {
|
|
419
|
+
const parsed = validateAndParseEmailForLookup(email);
|
|
420
|
+
if (!parsed.ok) {
|
|
444
421
|
return {
|
|
445
422
|
provider: null,
|
|
446
|
-
email,
|
|
423
|
+
email: parsed.email,
|
|
447
424
|
loginUrl: null,
|
|
448
|
-
error:
|
|
449
|
-
type: 'INVALID_EMAIL',
|
|
450
|
-
message: 'Invalid email format - missing domain'
|
|
451
|
-
}
|
|
425
|
+
error: parsed.error
|
|
452
426
|
};
|
|
453
427
|
}
|
|
428
|
+
const domain = parsed.domain;
|
|
429
|
+
const trimmedEmail = parsed.trimmedEmail;
|
|
454
430
|
// First try standard domain matching (fast path)
|
|
455
|
-
const syncResult = getEmailProviderSync(
|
|
431
|
+
const syncResult = getEmailProviderSync(trimmedEmail);
|
|
456
432
|
if (syncResult.provider) {
|
|
457
433
|
return {
|
|
458
434
|
...syncResult,
|
|
@@ -466,7 +442,7 @@ async function getEmailProviderFast(email, options = {}) {
|
|
|
466
442
|
if (!result.success) {
|
|
467
443
|
return {
|
|
468
444
|
provider: null,
|
|
469
|
-
email,
|
|
445
|
+
email: trimmedEmail,
|
|
470
446
|
loginUrl: null,
|
|
471
447
|
error: {
|
|
472
448
|
type: 'NETWORK_ERROR',
|
|
@@ -482,7 +458,7 @@ async function getEmailProviderFast(email, options = {}) {
|
|
|
482
458
|
});
|
|
483
459
|
const fastResult = {
|
|
484
460
|
provider: concurrentResult.provider,
|
|
485
|
-
email,
|
|
461
|
+
email: trimmedEmail,
|
|
486
462
|
loginUrl: concurrentResult.provider?.loginUrl || null,
|
|
487
463
|
detectionMethod: concurrentResult.detectionMethod || 'mx_record',
|
|
488
464
|
timing: concurrentResult.timing,
|
|
@@ -505,7 +481,7 @@ async function getEmailProviderFast(email, options = {}) {
|
|
|
505
481
|
loginUrl: null,
|
|
506
482
|
error: {
|
|
507
483
|
type: 'NETWORK_ERROR',
|
|
508
|
-
message: error.message
|
|
484
|
+
message: error instanceof Error ? error.message : 'DNS detection failed'
|
|
509
485
|
}
|
|
510
486
|
};
|
|
511
487
|
}
|
|
@@ -516,7 +492,7 @@ async function getEmailProviderFast(email, options = {}) {
|
|
|
516
492
|
exports.Config = {
|
|
517
493
|
DEFAULT_DNS_TIMEOUT: 5000,
|
|
518
494
|
MAX_DNS_REQUESTS_PER_MINUTE: 10,
|
|
519
|
-
SUPPORTED_PROVIDERS_COUNT:
|
|
520
|
-
SUPPORTED_DOMAINS_COUNT:
|
|
495
|
+
SUPPORTED_PROVIDERS_COUNT: 130,
|
|
496
|
+
SUPPORTED_DOMAINS_COUNT: 218
|
|
521
497
|
};
|
|
522
498
|
//# sourceMappingURL=api.js.map
|
package/dist/concurrent-dns.d.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* Uses Promise.allSettled for fault tolerance and intelligent result merging.
|
|
6
6
|
*/
|
|
7
7
|
import { EmailProvider } from './api';
|
|
8
|
+
type MXRecordLike = {
|
|
9
|
+
exchange?: string;
|
|
10
|
+
};
|
|
11
|
+
type TXTRecordLike = string;
|
|
8
12
|
/**
|
|
9
13
|
* Configuration for concurrent DNS detection
|
|
10
14
|
*/
|
|
@@ -29,13 +33,13 @@ export interface DNSQueryResult {
|
|
|
29
33
|
/** Whether the query succeeded */
|
|
30
34
|
success: boolean;
|
|
31
35
|
/** DNS records returned (if successful) */
|
|
32
|
-
records?:
|
|
36
|
+
records?: MXRecordLike[] | TXTRecordLike[];
|
|
33
37
|
/** Error information (if failed) */
|
|
34
38
|
error?: Error;
|
|
35
39
|
/** Query execution time in milliseconds */
|
|
36
40
|
timing: number;
|
|
37
41
|
/** Raw DNS response for debugging */
|
|
38
|
-
rawResponse?:
|
|
42
|
+
rawResponse?: unknown;
|
|
39
43
|
}
|
|
40
44
|
/**
|
|
41
45
|
* Result from concurrent DNS detection
|
|
@@ -130,4 +134,5 @@ export declare function createConcurrentDNSDetector(providers: EmailProvider[],
|
|
|
130
134
|
* Utility function for quick concurrent DNS detection
|
|
131
135
|
*/
|
|
132
136
|
export declare function detectProviderConcurrent(domain: string, providers: EmailProvider[], config?: Partial<ConcurrentDNSConfig>): Promise<ConcurrentDNSResult>;
|
|
137
|
+
export {};
|
|
133
138
|
//# sourceMappingURL=concurrent-dns.d.ts.map
|
package/dist/concurrent-dns.js
CHANGED
|
@@ -295,6 +295,8 @@ class ConcurrentDNSDetector {
|
|
|
295
295
|
let confidence = 0;
|
|
296
296
|
if (query.type === 'mx' && detection.mxPatterns) {
|
|
297
297
|
for (const record of query.records) {
|
|
298
|
+
if (typeof record !== 'object' || record === null)
|
|
299
|
+
continue;
|
|
298
300
|
const exchange = record.exchange?.toLowerCase() || '';
|
|
299
301
|
for (const pattern of detection.mxPatterns) {
|
|
300
302
|
if (exchange.includes(pattern.toLowerCase())) {
|
|
@@ -306,6 +308,8 @@ class ConcurrentDNSDetector {
|
|
|
306
308
|
}
|
|
307
309
|
else if (query.type === 'txt' && detection.txtPatterns) {
|
|
308
310
|
for (const record of query.records) {
|
|
311
|
+
if (typeof record !== 'string')
|
|
312
|
+
continue;
|
|
309
313
|
const txtRecord = record.toLowerCase();
|
|
310
314
|
for (const pattern of detection.txtPatterns) {
|
|
311
315
|
if (txtRecord.includes(pattern.toLowerCase())) {
|
|
@@ -368,6 +372,8 @@ class ConcurrentDNSDetector {
|
|
|
368
372
|
if (!mxQuery?.records)
|
|
369
373
|
return null;
|
|
370
374
|
for (const record of mxQuery.records) {
|
|
375
|
+
if (typeof record !== 'object' || record === null)
|
|
376
|
+
continue;
|
|
371
377
|
const exchange = record.exchange?.toLowerCase() || '';
|
|
372
378
|
for (const provider of this.providers) {
|
|
373
379
|
if (provider.type === 'proxy_service' && provider.customDomainDetection?.mxPatterns) {
|
|
@@ -402,9 +408,11 @@ class ConcurrentDNSDetector {
|
|
|
402
408
|
return Promise.reject(new Error(`DNS query timeout after ${ms}ms`));
|
|
403
409
|
}
|
|
404
410
|
let rejectFn;
|
|
411
|
+
let queryEntry;
|
|
405
412
|
const wrappedPromise = new Promise((resolve, reject) => {
|
|
406
413
|
rejectFn = reject;
|
|
407
|
-
const timeout = setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms)
|
|
414
|
+
const timeout = setTimeout(() => reject(new Error(`DNS query timeout after ${ms}ms`)), ms);
|
|
415
|
+
timeout.unref?.();
|
|
408
416
|
// Start the underlying operation only after setting up the timeout
|
|
409
417
|
fn()
|
|
410
418
|
.then(resolve)
|
|
@@ -412,7 +420,6 @@ class ConcurrentDNSDetector {
|
|
|
412
420
|
.finally(() => {
|
|
413
421
|
clearTimeout(timeout);
|
|
414
422
|
// Clean up active query
|
|
415
|
-
const queryEntry = Array.from(this.activeQueries).find(entry => entry.promise === wrappedPromise);
|
|
416
423
|
if (queryEntry) {
|
|
417
424
|
this.activeQueries.delete(queryEntry);
|
|
418
425
|
}
|
|
@@ -420,7 +427,7 @@ class ConcurrentDNSDetector {
|
|
|
420
427
|
});
|
|
421
428
|
// Track active query for potential cleanup in tests
|
|
422
429
|
if (rejectFn) {
|
|
423
|
-
|
|
430
|
+
queryEntry = { promise: wrappedPromise, reject: rejectFn };
|
|
424
431
|
this.activeQueries.add(queryEntry);
|
|
425
432
|
}
|
|
426
433
|
return wrappedPromise;
|
package/dist/hash-verifier.d.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* Provides cryptographic integrity verification for the email providers database
|
|
5
5
|
* to detect tampering or unauthorized modifications.
|
|
6
6
|
*/
|
|
7
|
+
type ProviderLike = {
|
|
8
|
+
companyProvider?: string;
|
|
9
|
+
loginUrl?: string | null;
|
|
10
|
+
};
|
|
7
11
|
export interface HashVerificationResult {
|
|
8
12
|
isValid: boolean;
|
|
9
13
|
expectedHash?: string;
|
|
@@ -40,7 +44,7 @@ export declare function verifyProvidersIntegrity(filePath: string, expectedHash?
|
|
|
40
44
|
* @param expectedHash - Expected hash of the JSON string
|
|
41
45
|
* @returns Verification result
|
|
42
46
|
*/
|
|
43
|
-
export declare function verifyProvidersDataIntegrity(providersData:
|
|
47
|
+
export declare function verifyProvidersDataIntegrity(providersData: unknown, expectedHash?: string): HashVerificationResult;
|
|
44
48
|
/**
|
|
45
49
|
* Generates security hashes for critical files - use this during development
|
|
46
50
|
*
|
|
@@ -85,10 +89,11 @@ export declare function performSecurityAudit(providersFilePath?: string): {
|
|
|
85
89
|
* @param providers - Array of email providers
|
|
86
90
|
* @returns Signed manifest with URL hashes
|
|
87
91
|
*/
|
|
88
|
-
export declare function createProviderManifest(providers:
|
|
92
|
+
export declare function createProviderManifest(providers: ProviderLike[]): {
|
|
89
93
|
timestamp: string;
|
|
90
94
|
providerCount: number;
|
|
91
95
|
urlHashes: Record<string, string>;
|
|
92
96
|
manifestHash: string;
|
|
93
97
|
};
|
|
98
|
+
export {};
|
|
94
99
|
//# sourceMappingURL=hash-verifier.d.ts.map
|
package/dist/hash-verifier.js
CHANGED
|
@@ -27,9 +27,9 @@ const path_1 = require("path");
|
|
|
27
27
|
*/
|
|
28
28
|
const KNOWN_GOOD_HASHES = {
|
|
29
29
|
// SHA-256 hash of the legitimate emailproviders.json
|
|
30
|
-
'emailproviders.json': '
|
|
30
|
+
'emailproviders.json': 'a4fe056edad44ae5479cc100d5cc67cb5f6df86e19c4209db6c5f715f5bf070e',
|
|
31
31
|
// You can add hashes for other critical files
|
|
32
|
-
'package.json': '
|
|
32
|
+
'package.json': '4133771103c0b1600b7d19c2290822eaeaaa9b1b84ef42965de4066545e1e960',
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* Calculates SHA-256 hash of a file or string content
|
|
@@ -96,8 +96,17 @@ function verifyProvidersIntegrity(filePath, expectedHash) {
|
|
|
96
96
|
*/
|
|
97
97
|
function verifyProvidersDataIntegrity(providersData, expectedHash) {
|
|
98
98
|
try {
|
|
99
|
+
if (providersData === null || typeof providersData !== 'object') {
|
|
100
|
+
return {
|
|
101
|
+
isValid: false,
|
|
102
|
+
actualHash: '',
|
|
103
|
+
reason: 'Invalid providers data format',
|
|
104
|
+
file: 'providersData'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
99
107
|
// Create deterministic JSON string (sorted keys)
|
|
100
|
-
const
|
|
108
|
+
const providersObject = providersData;
|
|
109
|
+
const jsonString = JSON.stringify(providersObject, Object.keys(providersObject).sort(), 2);
|
|
101
110
|
const actualHash = calculateHash(jsonString);
|
|
102
111
|
const expectedHashToUse = expectedHash || KNOWN_GOOD_HASHES['emailproviders.json'];
|
|
103
112
|
if (expectedHashToUse === 'TO_BE_CALCULATED') {
|
|
@@ -266,7 +275,7 @@ function createProviderManifest(providers) {
|
|
|
266
275
|
const urlHashes = {};
|
|
267
276
|
for (const provider of providers) {
|
|
268
277
|
if (provider.loginUrl) {
|
|
269
|
-
const key = `${provider.companyProvider}::${provider.loginUrl}`;
|
|
278
|
+
const key = `${provider.companyProvider ?? 'Unknown'}::${provider.loginUrl}`;
|
|
270
279
|
urlHashes[key] = calculateHash(provider.loginUrl);
|
|
271
280
|
}
|
|
272
281
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Email Provider Links
|
|
3
3
|
*
|
|
4
4
|
* A modern, robust email provider detection library with:
|
|
5
|
-
* -
|
|
5
|
+
* - 130+ verified email providers covering 210+ domains
|
|
6
6
|
* - Concurrent DNS detection for business domains
|
|
7
7
|
* - Zero runtime dependencies
|
|
8
8
|
* - Comprehensive error handling with detailed context
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @author Email Provider Links Team
|
|
14
14
|
* @license MIT
|
|
15
|
-
* @version
|
|
15
|
+
* @version See package.json
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
import { loadProviders } from './loader';
|
|
18
|
+
import { getEmailProvider, getEmailProviderSync, getEmailProviderFast, normalizeEmail, emailsMatch, Config } from './api';
|
|
19
|
+
import { detectProviderConcurrent } from './concurrent-dns';
|
|
20
|
+
import { validateInternationalEmail, emailToPunycode, domainToPunycode } from './idn';
|
|
21
|
+
export { getEmailProvider, getEmailProviderSync, getEmailProviderFast, normalizeEmail, emailsMatch, Config };
|
|
18
22
|
export type { EmailProvider, EmailProviderResult } from './api';
|
|
19
|
-
export { loadProviders
|
|
20
|
-
export { detectProviderConcurrent } from './concurrent-dns';
|
|
21
|
-
export { validateInternationalEmail, emailToPunycode, domainToPunycode } from './idn';
|
|
23
|
+
export { loadProviders, detectProviderConcurrent, validateInternationalEmail, emailToPunycode, domainToPunycode };
|
|
22
24
|
export type { ConcurrentDNSConfig, ConcurrentDNSResult } from './concurrent-dns';
|
|
23
25
|
/**
|
|
24
26
|
* Enhanced email validation with comprehensive error reporting
|
|
@@ -45,10 +47,6 @@ export declare function validateEmailAddress(email: string): {
|
|
|
45
47
|
message: string;
|
|
46
48
|
};
|
|
47
49
|
};
|
|
48
|
-
import { loadProviders } from './loader';
|
|
49
|
-
import { getEmailProvider, getEmailProviderSync, getEmailProviderFast, normalizeEmail, emailsMatch } from './api';
|
|
50
|
-
import { detectProviderConcurrent } from './concurrent-dns';
|
|
51
|
-
import { validateInternationalEmail } from './idn';
|
|
52
50
|
/**
|
|
53
51
|
* Get comprehensive list of all supported email providers
|
|
54
52
|
*
|
|
@@ -169,8 +167,8 @@ export declare const isValidEmailAddress: typeof isValidEmail;
|
|
|
169
167
|
/**
|
|
170
168
|
* Library metadata (legacy constants)
|
|
171
169
|
*/
|
|
172
|
-
export declare const PROVIDER_COUNT
|
|
173
|
-
export declare const DOMAIN_COUNT
|
|
170
|
+
export declare const PROVIDER_COUNT: 130;
|
|
171
|
+
export declare const DOMAIN_COUNT: 218;
|
|
174
172
|
/**
|
|
175
173
|
* Default export for convenience
|
|
176
174
|
*
|
|
@@ -200,15 +198,12 @@ declare const _default: {
|
|
|
200
198
|
Config: {
|
|
201
199
|
readonly DEFAULT_DNS_TIMEOUT: 5000;
|
|
202
200
|
readonly MAX_DNS_REQUESTS_PER_MINUTE: 10;
|
|
203
|
-
readonly SUPPORTED_PROVIDERS_COUNT:
|
|
204
|
-
readonly SUPPORTED_DOMAINS_COUNT:
|
|
201
|
+
readonly SUPPORTED_PROVIDERS_COUNT: 130;
|
|
202
|
+
readonly SUPPORTED_DOMAINS_COUNT: 218;
|
|
205
203
|
};
|
|
206
|
-
PROVIDER_COUNT:
|
|
207
|
-
DOMAIN_COUNT:
|
|
204
|
+
PROVIDER_COUNT: 130;
|
|
205
|
+
DOMAIN_COUNT: 218;
|
|
208
206
|
};
|
|
209
207
|
export default _default;
|
|
210
|
-
|
|
211
|
-
* Version information
|
|
212
|
-
*/
|
|
213
|
-
export declare const VERSION = "2.7.0";
|
|
208
|
+
export declare const VERSION: string;
|
|
214
209
|
//# sourceMappingURL=index.d.ts.map
|