@mikkelscheike/email-provider-links 1.6.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 +133 -35
- package/dist/alias-detection.d.ts +58 -0
- package/dist/alias-detection.js +245 -0
- 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 -349
- package/dist/index.js +65 -576
- 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 +7 -2
- package/providers/emailproviders.json +634 -428
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Data Loader
|
|
3
|
+
*
|
|
4
|
+
* Handles loading email provider data with performance optimizations.
|
|
5
|
+
*/
|
|
6
|
+
import { EmailProvider } from './index';
|
|
7
|
+
/**
|
|
8
|
+
* Loading statistics for performance monitoring
|
|
9
|
+
*/
|
|
10
|
+
interface LoadingStats {
|
|
11
|
+
fileSize: number;
|
|
12
|
+
loadTime: number;
|
|
13
|
+
providerCount: number;
|
|
14
|
+
domainCount: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build optimized domain-to-provider lookup map
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildDomainMap(providers: EmailProvider[]): Map<string, EmailProvider>;
|
|
20
|
+
/**
|
|
21
|
+
* Clear all caches (useful for testing or hot reloading)
|
|
22
|
+
*/
|
|
23
|
+
export declare function clearCache(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Get loading statistics from the last load operation
|
|
26
|
+
*/
|
|
27
|
+
export declare function getLoadingStats(): LoadingStats | null;
|
|
28
|
+
/**
|
|
29
|
+
* Load all providers with optimized domain map for production
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadProviders(): {
|
|
32
|
+
providers: EmailProvider[];
|
|
33
|
+
domainMap: Map<string, EmailProvider>;
|
|
34
|
+
stats: LoadingStats;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Load providers with debug information
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadProvidersDebug(): {
|
|
40
|
+
providers: EmailProvider[];
|
|
41
|
+
domainMap: Map<string, EmailProvider>;
|
|
42
|
+
stats: LoadingStats;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Provider Data Loader
|
|
4
|
+
*
|
|
5
|
+
* Handles loading email provider data with performance optimizations.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.buildDomainMap = buildDomainMap;
|
|
9
|
+
exports.clearCache = clearCache;
|
|
10
|
+
exports.getLoadingStats = getLoadingStats;
|
|
11
|
+
exports.loadProviders = loadProviders;
|
|
12
|
+
exports.loadProvidersDebug = loadProvidersDebug;
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
const schema_1 = require("./schema");
|
|
16
|
+
/**
|
|
17
|
+
* Internal cached data
|
|
18
|
+
*/
|
|
19
|
+
let cachedProviders = null;
|
|
20
|
+
let cachedDomainMap = null;
|
|
21
|
+
let loadingStats = null;
|
|
22
|
+
/**
|
|
23
|
+
* Default loader configuration
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_CONFIG = {
|
|
26
|
+
debug: false
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Convert compressed provider to EmailProvider format
|
|
30
|
+
*/
|
|
31
|
+
function convertProviderToEmailProvider(compressedProvider) {
|
|
32
|
+
const provider = {
|
|
33
|
+
companyProvider: compressedProvider.name,
|
|
34
|
+
loginUrl: compressedProvider.url,
|
|
35
|
+
domains: compressedProvider.domains || []
|
|
36
|
+
};
|
|
37
|
+
// Convert DNS detection patterns
|
|
38
|
+
if (compressedProvider.mx?.length || compressedProvider.txt?.length) {
|
|
39
|
+
provider.customDomainDetection = {};
|
|
40
|
+
if (compressedProvider.mx?.length) {
|
|
41
|
+
provider.customDomainDetection.mxPatterns = compressedProvider.mx;
|
|
42
|
+
}
|
|
43
|
+
if (compressedProvider.txt?.length) {
|
|
44
|
+
// Decompress TXT patterns
|
|
45
|
+
provider.customDomainDetection.txtPatterns = compressedProvider.txt.map(schema_1.decompressTxtPattern);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return provider;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Internal provider data loader with configuration
|
|
52
|
+
*/
|
|
53
|
+
function loadProvidersInternal(config = {}) {
|
|
54
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
// Return cached data if available
|
|
57
|
+
if (cachedProviders && !mergedConfig.debug) {
|
|
58
|
+
return {
|
|
59
|
+
providers: cachedProviders,
|
|
60
|
+
stats: loadingStats
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
// Determine file path
|
|
65
|
+
const basePath = (0, path_1.join)(__dirname, '..', 'providers');
|
|
66
|
+
const dataPath = mergedConfig.path || (0, path_1.join)(basePath, 'emailproviders.json');
|
|
67
|
+
if (mergedConfig.debug)
|
|
68
|
+
console.log('🔄 Loading provider data...');
|
|
69
|
+
const content = (0, fs_1.readFileSync)(dataPath, 'utf8');
|
|
70
|
+
const data = JSON.parse(content);
|
|
71
|
+
// Validate format
|
|
72
|
+
if (!data.version || !data.providers || !Array.isArray(data.providers)) {
|
|
73
|
+
throw new Error('Invalid provider data format');
|
|
74
|
+
}
|
|
75
|
+
const providers = data.providers.map(convertProviderToEmailProvider);
|
|
76
|
+
const fileSize = content.length;
|
|
77
|
+
if (mergedConfig.debug) {
|
|
78
|
+
console.log(`✅ Loaded ${providers.length} providers`);
|
|
79
|
+
console.log(`📊 File size: ${(fileSize / 1024).toFixed(1)} KB`);
|
|
80
|
+
}
|
|
81
|
+
const loadTime = Date.now() - startTime;
|
|
82
|
+
const domainCount = providers.reduce((sum, p) => sum + p.domains.length, 0);
|
|
83
|
+
// Cache the results
|
|
84
|
+
cachedProviders = providers;
|
|
85
|
+
loadingStats = {
|
|
86
|
+
fileSize,
|
|
87
|
+
loadTime,
|
|
88
|
+
providerCount: providers.length,
|
|
89
|
+
domainCount
|
|
90
|
+
};
|
|
91
|
+
if (mergedConfig.debug) {
|
|
92
|
+
console.log(`⚡ Loading completed in ${loadTime}ms`);
|
|
93
|
+
console.log(`📊 Stats: ${providers.length} providers, ${domainCount} domains`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
providers,
|
|
97
|
+
stats: loadingStats
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
102
|
+
throw new Error(`Failed to load provider data: ${errorMessage}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build optimized domain-to-provider lookup map
|
|
107
|
+
*/
|
|
108
|
+
function buildDomainMap(providers) {
|
|
109
|
+
if (cachedDomainMap) {
|
|
110
|
+
return cachedDomainMap;
|
|
111
|
+
}
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
const domainMap = new Map();
|
|
114
|
+
for (const provider of providers) {
|
|
115
|
+
for (const domain of provider.domains) {
|
|
116
|
+
domainMap.set(domain.toLowerCase(), provider);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
cachedDomainMap = domainMap;
|
|
120
|
+
if (loadingStats) {
|
|
121
|
+
console.log(`🗺️ Domain map built in ${Date.now() - startTime}ms (${domainMap.size} entries)`);
|
|
122
|
+
}
|
|
123
|
+
return domainMap;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Clear all caches (useful for testing or hot reloading)
|
|
127
|
+
*/
|
|
128
|
+
function clearCache() {
|
|
129
|
+
cachedProviders = null;
|
|
130
|
+
cachedDomainMap = null;
|
|
131
|
+
loadingStats = null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get loading statistics from the last load operation
|
|
135
|
+
*/
|
|
136
|
+
function getLoadingStats() {
|
|
137
|
+
return loadingStats;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Load all providers with optimized domain map for production
|
|
141
|
+
*/
|
|
142
|
+
function loadProviders() {
|
|
143
|
+
const { providers, stats } = loadProvidersInternal({ debug: false });
|
|
144
|
+
const domainMap = buildDomainMap(providers);
|
|
145
|
+
return { providers, domainMap, stats };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Load providers with debug information
|
|
149
|
+
*/
|
|
150
|
+
function loadProvidersDebug() {
|
|
151
|
+
clearCache(); // Always reload in debug mode
|
|
152
|
+
const { providers, stats } = loadProvidersInternal({ debug: true });
|
|
153
|
+
const domainMap = buildDomainMap(providers);
|
|
154
|
+
return { providers, domainMap, stats };
|
|
155
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Data Schema
|
|
3
|
+
*
|
|
4
|
+
* This represents the compressed format for provider data
|
|
5
|
+
* designed to reduce file size and improve parsing performance.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Provider interface
|
|
9
|
+
* Uses compact field names for smaller JSON size
|
|
10
|
+
*/
|
|
11
|
+
export interface Provider {
|
|
12
|
+
/** Provider ID (short identifier) */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Provider display name */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Login/webmail URL */
|
|
17
|
+
url: string;
|
|
18
|
+
/** Email domains (omitted if empty) */
|
|
19
|
+
domains?: string[];
|
|
20
|
+
/** DNS detection patterns (flattened) */
|
|
21
|
+
mx?: string[];
|
|
22
|
+
txt?: string[];
|
|
23
|
+
/** Alias capabilities */
|
|
24
|
+
alias?: {
|
|
25
|
+
dots?: boolean;
|
|
26
|
+
plus?: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Providers file structure
|
|
31
|
+
*/
|
|
32
|
+
export interface ProvidersData {
|
|
33
|
+
/** Schema version for future migrations */
|
|
34
|
+
version: string;
|
|
35
|
+
/** Compressed providers array */
|
|
36
|
+
providers: Provider[];
|
|
37
|
+
/** Metadata */
|
|
38
|
+
meta: {
|
|
39
|
+
count: number;
|
|
40
|
+
domains: number;
|
|
41
|
+
generated: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Common TXT pattern prefixes that can be compressed
|
|
46
|
+
*/
|
|
47
|
+
export declare const TXT_PATTERN_COMPRESSION: {
|
|
48
|
+
readonly 'v=spf1 include:': "spf:";
|
|
49
|
+
readonly 'v=spf1 ': "spf1:";
|
|
50
|
+
readonly 'google-site-verification=': "gsv:";
|
|
51
|
+
readonly 'MS=ms': "ms";
|
|
52
|
+
readonly 'zoho-verification=': "zv:";
|
|
53
|
+
readonly 'mailgun-verification=': "mv:";
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Compress TXT patterns by removing common prefixes
|
|
57
|
+
*/
|
|
58
|
+
export declare function compressTxtPattern(pattern: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Decompress TXT patterns by restoring prefixes
|
|
61
|
+
*/
|
|
62
|
+
export declare function decompressTxtPattern(compressed: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Validation schema for providers
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateProvider(provider: Provider): string[];
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Provider Data Schema
|
|
4
|
+
*
|
|
5
|
+
* This represents the compressed format for provider data
|
|
6
|
+
* designed to reduce file size and improve parsing performance.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.TXT_PATTERN_COMPRESSION = void 0;
|
|
10
|
+
exports.compressTxtPattern = compressTxtPattern;
|
|
11
|
+
exports.decompressTxtPattern = decompressTxtPattern;
|
|
12
|
+
exports.validateProvider = validateProvider;
|
|
13
|
+
/**
|
|
14
|
+
* Common TXT pattern prefixes that can be compressed
|
|
15
|
+
*/
|
|
16
|
+
exports.TXT_PATTERN_COMPRESSION = {
|
|
17
|
+
'v=spf1 include:': 'spf:',
|
|
18
|
+
'v=spf1 ': 'spf1:',
|
|
19
|
+
'google-site-verification=': 'gsv:',
|
|
20
|
+
'MS=ms': 'ms',
|
|
21
|
+
'zoho-verification=': 'zv:',
|
|
22
|
+
'mailgun-verification=': 'mv:'
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Compress TXT patterns by removing common prefixes
|
|
26
|
+
*/
|
|
27
|
+
function compressTxtPattern(pattern) {
|
|
28
|
+
for (const [prefix, compressed] of Object.entries(exports.TXT_PATTERN_COMPRESSION)) {
|
|
29
|
+
if (pattern.startsWith(prefix)) {
|
|
30
|
+
return compressed + pattern.substring(prefix.length);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return pattern;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decompress TXT patterns by restoring prefixes
|
|
37
|
+
*/
|
|
38
|
+
function decompressTxtPattern(compressed) {
|
|
39
|
+
for (const [prefix, compressedPrefix] of Object.entries(exports.TXT_PATTERN_COMPRESSION)) {
|
|
40
|
+
if (compressed.startsWith(compressedPrefix)) {
|
|
41
|
+
return prefix + compressed.substring(compressedPrefix.length);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return compressed;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validation schema for providers
|
|
48
|
+
*/
|
|
49
|
+
function validateProvider(provider) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
if (!provider.id || typeof provider.id !== 'string') {
|
|
52
|
+
errors.push('Provider ID is required and must be a string');
|
|
53
|
+
}
|
|
54
|
+
if (!provider.name || typeof provider.name !== 'string') {
|
|
55
|
+
errors.push('Provider name is required and must be a string');
|
|
56
|
+
}
|
|
57
|
+
if (!provider.url || typeof provider.url !== 'string') {
|
|
58
|
+
errors.push('Provider URL is required and must be a string');
|
|
59
|
+
}
|
|
60
|
+
else if (!provider.url.startsWith('https://')) {
|
|
61
|
+
errors.push('Provider URL must use HTTPS');
|
|
62
|
+
}
|
|
63
|
+
if (provider.domains && !Array.isArray(provider.domains)) {
|
|
64
|
+
errors.push('Domains must be an array');
|
|
65
|
+
}
|
|
66
|
+
if (provider.mx && !Array.isArray(provider.mx)) {
|
|
67
|
+
errors.push('MX patterns must be an array');
|
|
68
|
+
}
|
|
69
|
+
if (provider.txt && !Array.isArray(provider.txt)) {
|
|
70
|
+
errors.push('TXT patterns must be an array');
|
|
71
|
+
}
|
|
72
|
+
return errors;
|
|
73
|
+
}
|
|
@@ -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': 'f77814bf0537019c6f38bf2744ae21640f04a2d39cb67c5116f6e03160c9486f',
|
|
31
31
|
// You can add hashes for other critical files
|
|
32
|
-
'package.json': '
|
|
32
|
+
'package.json': '874ac6e2398fcc48e373bd2f7d06a7bd861725c5a299d5d1b6beac92490d037d'
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* Calculates SHA-256 hash of a file or string content
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikkelscheike/email-provider-links",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "TypeScript library for email provider detection with 93 providers (178 domains), concurrent DNS resolution, optimized performance, 91.75% test coverage, and enterprise security for login and password reset flows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"gmail",
|
|
31
31
|
"outlook",
|
|
32
32
|
"yahoo",
|
|
33
|
+
"alias-detection",
|
|
34
|
+
"email-normalization",
|
|
35
|
+
"plus-addressing",
|
|
36
|
+
"fraud-prevention",
|
|
37
|
+
"deduplication",
|
|
33
38
|
"typescript",
|
|
34
39
|
"npm",
|
|
35
40
|
"utility"
|