@mikkelscheike/email-provider-links 4.0.11 → 5.1.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.
@@ -10,47 +10,29 @@ exports.clearCache = clearCache;
10
10
  exports.loadProviders = loadProviders;
11
11
  exports.initializeSecurity = initializeSecurity;
12
12
  exports.createSecurityMiddleware = createSecurityMiddleware;
13
- const fs_1 = require("fs");
13
+ exports.buildDomainMap = buildDomainMap;
14
+ exports.getLoadingStats = getLoadingStats;
15
+ exports.loadProvidersDebug = loadProvidersDebug;
14
16
  const path_1 = require("path");
17
+ const fs_1 = require("fs");
15
18
  const url_validator_1 = require("./url-validator");
16
19
  const hash_verifier_1 = require("./hash-verifier");
17
- const schema_1 = require("./schema");
20
+ const error_utils_1 = require("./error-utils");
21
+ const constants_1 = require("./constants");
22
+ const provider_store_1 = require("./provider-store");
18
23
  // Cache for load results
19
24
  let cachedLoadResult = null;
25
+ // Cache for loading statistics
26
+ let loadingStats = null;
27
+ // Cache for domain maps
28
+ let cachedDomainMap = null;
20
29
  /**
21
30
  * Clear the cache (useful for testing or when providers file changes)
22
31
  */
23
32
  function clearCache() {
24
33
  cachedLoadResult = null;
25
- }
26
- /**
27
- * Convert compressed provider to EmailProvider format
28
- */
29
- function convertProviderToEmailProvider(compressedProvider) {
30
- if (!compressedProvider.type) {
31
- console.warn(`Missing type for provider ${compressedProvider.id}`);
32
- }
33
- const provider = {
34
- companyProvider: compressedProvider.companyProvider,
35
- loginUrl: compressedProvider.loginUrl || null,
36
- domains: compressedProvider.domains || [],
37
- type: compressedProvider.type,
38
- alias: compressedProvider.alias
39
- };
40
- // Include DNS detection patterns for business email services and proxy services
41
- const needsCustomDomainDetection = compressedProvider.type === 'custom_provider' ||
42
- compressedProvider.type === 'proxy_service';
43
- if (needsCustomDomainDetection && (compressedProvider.mx?.length || compressedProvider.txt?.length)) {
44
- provider.customDomainDetection = {};
45
- if (compressedProvider.mx?.length) {
46
- provider.customDomainDetection.mxPatterns = compressedProvider.mx;
47
- }
48
- if (compressedProvider.txt?.length) {
49
- // Decompress TXT patterns
50
- provider.customDomainDetection.txtPatterns = compressedProvider.txt.map(schema_1.decompressTxtPattern);
51
- }
52
- }
53
- return provider;
34
+ loadingStats = null;
35
+ cachedDomainMap = null;
54
36
  }
55
37
  /**
56
38
  * Loads and validates email provider data with security checks
@@ -83,39 +65,62 @@ function loadProviders(providersPath, expectedHash) {
83
65
  }
84
66
  // Step 2: Load and parse JSON
85
67
  try {
86
- const fileContent = (0, fs_1.readFileSync)(filePath, 'utf8');
87
- const data = JSON.parse(fileContent);
88
- // Validate format
89
- if (!data.version || !data.providers || !Array.isArray(data.providers)) {
90
- throw new Error('Invalid provider data format');
68
+ const { data } = (0, provider_store_1.readProvidersDataFile)(filePath);
69
+ providers = data.providers.map(provider_store_1.convertProviderToEmailProviderShared);
70
+ // Log memory usage in development mode
71
+ if (process.env.NODE_ENV === 'development' && !process.env.JEST_WORKER_ID) {
72
+ const memUsage = process.memoryUsage();
73
+ const memUsageMB = (memUsage.heapUsed / constants_1.MemoryConstants.BYTES_PER_KB / constants_1.MemoryConstants.KB_PER_MB).toFixed(2);
74
+ console.log(`🚀 Current memory usage: ${memUsageMB} MB`);
91
75
  }
92
- // Convert to EmailProvider format
93
- providers = data.providers.map(convertProviderToEmailProvider);
94
76
  }
95
77
  catch (error) {
96
- issues.push(`Failed to load providers file: ${error instanceof Error ? error.message : 'Unknown error'}`);
97
- return {
98
- success: false,
99
- providers: [],
100
- securityReport: {
101
- hashVerification: false,
102
- urlValidation: false,
103
- totalProviders: 0,
104
- validUrls: 0,
105
- invalidUrls: 0,
106
- securityLevel: 'CRITICAL',
107
- issues
78
+ // Use standardized error handling utilities
79
+ const errorMessage = (0, error_utils_1.getErrorMessage)(error);
80
+ const fileNotFound = (0, error_utils_1.isFileNotFoundError)(error);
81
+ const jsonError = (0, error_utils_1.isJsonError)(error);
82
+ // Return error result for JSON parse errors and file not found (ENOENT)
83
+ // This allows security tests to check error handling
84
+ // Note: ENOENT errors are already handled by hash verification, but we still need to handle
85
+ // them here in case hash verification passed but file was deleted between verification and read
86
+ if (jsonError || fileNotFound) {
87
+ if (!jsonError) {
88
+ // For file not found, don't add duplicate issue if hash verification already failed
89
+ if (hashResult.isValid) {
90
+ issues.push(`Failed to load providers file: ${errorMessage}`);
91
+ }
108
92
  }
109
- };
93
+ else {
94
+ issues.push(`Failed to load providers file: ${errorMessage}`);
95
+ }
96
+ return {
97
+ success: false,
98
+ providers: [],
99
+ securityReport: {
100
+ hashVerification: hashResult.isValid,
101
+ urlValidation: false,
102
+ totalProviders: 0,
103
+ validUrls: 0,
104
+ invalidUrls: 0,
105
+ securityLevel: 'CRITICAL',
106
+ issues
107
+ }
108
+ };
109
+ }
110
+ // For other errors, add to issues and throw (to match loader.test.ts expectations)
111
+ issues.push(`Failed to load providers file: ${errorMessage}`);
112
+ throw new Error(`Failed to load provider data: ${errorMessage}`);
110
113
  }
111
114
  // Step 3: URL validation audit
112
115
  const urlAudit = (0, url_validator_1.auditProviderSecurity)(providers);
113
- if (urlAudit.invalid > 0) {
114
- issues.push(`${urlAudit.invalid} providers have invalid URLs`);
116
+ // Count only providers with invalid URLs (not providers without URLs)
117
+ const providersWithInvalidUrls = urlAudit.invalidProviders.filter(invalid => invalid.url !== '' && invalid.url !== undefined && invalid.url !== null);
118
+ if (providersWithInvalidUrls.length > 0) {
119
+ issues.push(`${providersWithInvalidUrls.length} providers have invalid URLs`);
115
120
  // Suppress logging during tests to avoid console noise
116
121
  if (process.env.NODE_ENV !== 'test' && !process.env.JEST_WORKER_ID) {
117
122
  console.warn('⚠️ URL validation issues found:');
118
- for (const invalid of urlAudit.invalidProviders) {
123
+ for (const invalid of providersWithInvalidUrls) {
119
124
  console.warn(`- ${invalid.provider}: ${invalid.validation.reason}`);
120
125
  }
121
126
  }
@@ -132,28 +137,39 @@ function loadProviders(providersPath, expectedHash) {
132
137
  issues.push(`Filtered out ${filtered} providers with invalid URLs`);
133
138
  }
134
139
  // Step 5: Determine security level
140
+ // Only providers with invalid URLs affect security level, not providers without URLs
135
141
  let securityLevel = 'SECURE';
136
142
  if (!hashResult.isValid) {
137
143
  securityLevel = 'CRITICAL';
138
144
  }
139
- else if (urlAudit.invalid > 0 || issues.length > 0) {
145
+ else if (providersWithInvalidUrls.length > 0 || issues.length > 0) {
140
146
  securityLevel = 'WARNING';
141
147
  }
142
148
  const loadResult = {
143
149
  success: securityLevel !== 'CRITICAL',
144
150
  providers: secureProviders,
151
+ domainMap: buildDomainMap(secureProviders),
152
+ stats: {
153
+ loadTime: 0, // Would need to track this during load
154
+ domainMapTime: 0,
155
+ providerCount: secureProviders.length,
156
+ domainCount: secureProviders.reduce((count, p) => count + (p.domains?.length || 0), 0),
157
+ fileSize: (0, fs_1.readFileSync)(filePath, 'utf8').length // Calculate actual file size in bytes
158
+ },
145
159
  securityReport: {
146
160
  hashVerification: hashResult.isValid,
147
- urlValidation: urlAudit.invalid === 0,
161
+ urlValidation: providersWithInvalidUrls.length === 0, // Only count providers with invalid URLs, not providers without URLs
148
162
  totalProviders: providers.length,
149
163
  validUrls: urlAudit.valid,
150
- invalidUrls: urlAudit.invalid,
164
+ invalidUrls: providersWithInvalidUrls.length, // Only count actual invalid URLs
151
165
  securityLevel,
152
166
  issues
153
167
  }
154
168
  };
155
169
  // Cache the result for future calls
156
170
  cachedLoadResult = loadResult;
171
+ // Update loading stats for getLoadingStats()
172
+ loadingStats = loadResult.stats;
157
173
  return loadResult;
158
174
  }
159
175
  /**
@@ -178,19 +194,79 @@ function createSecurityMiddleware(options = {}) {
178
194
  if (options.onSecurityIssue) {
179
195
  options.onSecurityIssue(result.securityReport);
180
196
  }
181
- return res.status(500).json({
197
+ res.status(500).json({
182
198
  error: 'Security validation failed',
183
199
  details: result.securityReport
184
200
  });
201
+ return;
185
202
  }
186
203
  // Attach secure providers to request
187
204
  req.secureProviders = result.providers;
188
205
  req.securityReport = result.securityReport;
189
206
  next();
207
+ return;
208
+ };
209
+ }
210
+ /**
211
+ * Build domain map from providers
212
+ */
213
+ function buildDomainMap(providers) {
214
+ // Return cached domain map if available
215
+ if (cachedDomainMap) {
216
+ return cachedDomainMap;
217
+ }
218
+ // Build and cache the domain map
219
+ cachedDomainMap = (0, provider_store_1.buildDomainMapShared)(providers);
220
+ return cachedDomainMap;
221
+ }
222
+ /**
223
+ * Get loading statistics from the last load operation
224
+ */
225
+ function getLoadingStats() {
226
+ return loadingStats;
227
+ }
228
+ /**
229
+ * Load providers with debug information (always reloads cache)
230
+ */
231
+ function loadProvidersDebug() {
232
+ const startTime = process.hrtime.bigint();
233
+ // Clear cache for debug mode - ensure we always reload
234
+ cachedLoadResult = null;
235
+ loadingStats = null;
236
+ const result = loadProviders();
237
+ const endTime = process.hrtime.bigint();
238
+ // Build domain map and calculate stats
239
+ const domainMapStart = process.hrtime.bigint();
240
+ const domainMap = buildDomainMap(result.providers);
241
+ const domainMapEnd = process.hrtime.bigint();
242
+ // Store loading stats
243
+ loadingStats = {
244
+ loadTime: Number(endTime - startTime) / 1000000, // Convert to milliseconds
245
+ domainMapTime: Number(domainMapEnd - domainMapStart) / 1000000,
246
+ providerCount: result.providers.length,
247
+ domainCount: domainMap.size,
248
+ fileSize: 0 // Would need to track this during load
249
+ };
250
+ // Debug output
251
+ console.log('=== Provider Loading Debug ===');
252
+ console.log(`Providers loaded: ${result.providers.length}`);
253
+ console.log(`Security level: ${result.securityReport.securityLevel}`);
254
+ console.log(`Load time: ${loadingStats.loadTime.toFixed(2)}ms`);
255
+ console.log(`Domain map time: ${loadingStats.domainMapTime.toFixed(2)}ms`);
256
+ console.log(`Total domains: ${loadingStats.domainCount}`);
257
+ console.log('=============================');
258
+ // Return enhanced result with debug info - ensure new objects each time
259
+ return {
260
+ ...result,
261
+ domainMap: new Map(domainMap), // Create new Map instance
262
+ stats: { ...loadingStats } // Create new stats object
190
263
  };
191
264
  }
192
265
  exports.default = {
193
266
  loadProviders,
267
+ loadProvidersDebug,
268
+ buildDomainMap,
269
+ getLoadingStats,
194
270
  initializeSecurity,
195
271
  createSecurityMiddleware,
196
272
  clearCache
@@ -0,0 +1,9 @@
1
+ import type { EmailProvider } from './api';
2
+ import { Provider, ProvidersData } from './schema';
3
+ export declare function convertProviderToEmailProviderShared(compressedProvider: Provider): EmailProvider;
4
+ export declare function readProvidersDataFile(filePath: string): {
5
+ data: ProvidersData;
6
+ fileSize: number;
7
+ };
8
+ export declare function buildDomainMapShared(providers: EmailProvider[]): Map<string, EmailProvider>;
9
+ //# sourceMappingURL=provider-store.d.ts.map
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertProviderToEmailProviderShared = convertProviderToEmailProviderShared;
4
+ exports.readProvidersDataFile = readProvidersDataFile;
5
+ exports.buildDomainMapShared = buildDomainMapShared;
6
+ const fs_1 = require("fs");
7
+ const schema_1 = require("./schema");
8
+ function convertProviderToEmailProviderShared(compressedProvider) {
9
+ if (!compressedProvider.type) {
10
+ console.warn(`Missing type for provider ${compressedProvider.id}`);
11
+ }
12
+ const provider = {
13
+ companyProvider: compressedProvider.companyProvider,
14
+ loginUrl: compressedProvider.loginUrl || null,
15
+ domains: compressedProvider.domains || [],
16
+ type: compressedProvider.type,
17
+ alias: compressedProvider.alias
18
+ };
19
+ const needsCustomDomainDetection = compressedProvider.type === 'custom_provider' ||
20
+ compressedProvider.type === 'proxy_service';
21
+ if (needsCustomDomainDetection && (compressedProvider.mx?.length || compressedProvider.txt?.length)) {
22
+ provider.customDomainDetection = {};
23
+ if (compressedProvider.mx?.length) {
24
+ provider.customDomainDetection.mxPatterns = compressedProvider.mx;
25
+ }
26
+ if (compressedProvider.txt?.length) {
27
+ provider.customDomainDetection.txtPatterns = compressedProvider.txt.map(schema_1.decompressTxtPattern);
28
+ }
29
+ }
30
+ return provider;
31
+ }
32
+ function readProvidersDataFile(filePath) {
33
+ const content = (0, fs_1.readFileSync)(filePath, 'utf8');
34
+ const data = JSON.parse(content);
35
+ if (!data.version || !data.providers || !Array.isArray(data.providers)) {
36
+ throw new Error('Invalid provider data format');
37
+ }
38
+ return { data, fileSize: content.length };
39
+ }
40
+ function buildDomainMapShared(providers) {
41
+ const domainMap = new Map();
42
+ for (const provider of providers) {
43
+ for (const domain of provider.domains) {
44
+ domainMap.set(domain.toLowerCase(), provider);
45
+ }
46
+ }
47
+ return domainMap;
48
+ }
49
+ //# sourceMappingURL=provider-store.js.map
@@ -4,6 +4,15 @@
4
4
  * Provides validation and allowlisting for email provider URLs to prevent
5
5
  * malicious redirects and supply chain attacks.
6
6
  */
7
+ /**
8
+ * Get allowlisted domains from provider data
9
+ * Only URLs from these domains will be considered safe.
10
+ */
11
+ export declare function getAllowedDomains(): Set<string>;
12
+ type ProviderUrlLike = {
13
+ companyProvider?: string;
14
+ loginUrl?: string | null;
15
+ };
7
16
  export interface URLValidationResult {
8
17
  isValid: boolean;
9
18
  reason?: string;
@@ -23,7 +32,7 @@ export declare function validateEmailProviderUrl(url: string): URLValidationResu
23
32
  * @param providers - Array of email providers to validate
24
33
  * @returns Array of validation results
25
34
  */
26
- export declare function validateAllProviderUrls(providers: any[]): Array<{
35
+ export declare function validateAllProviderUrls(providers: ProviderUrlLike[]): Array<{
27
36
  provider: string;
28
37
  url: string;
29
38
  validation: URLValidationResult;
@@ -34,7 +43,7 @@ export declare function validateAllProviderUrls(providers: any[]): Array<{
34
43
  * @param providers - Array of email providers to audit
35
44
  * @returns Security audit report
36
45
  */
37
- export declare function auditProviderSecurity(providers: any[]): {
46
+ export declare function auditProviderSecurity(providers: ProviderUrlLike[]): {
38
47
  total: number;
39
48
  valid: number;
40
49
  invalid: number;
@@ -45,4 +54,5 @@ export declare function auditProviderSecurity(providers: any[]): {
45
54
  }[];
46
55
  report: string;
47
56
  };
57
+ export {};
48
58
  //# sourceMappingURL=url-validator.d.ts.map
@@ -6,22 +6,35 @@
6
6
  * malicious redirects and supply chain attacks.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getAllowedDomains = getAllowedDomains;
9
10
  exports.validateEmailProviderUrl = validateEmailProviderUrl;
10
11
  exports.validateAllProviderUrls = validateAllProviderUrls;
11
12
  exports.auditProviderSecurity = auditProviderSecurity;
12
- const loader_1 = require("./loader");
13
+ const fs_1 = require("fs");
14
+ const path_1 = require("path");
15
+ const hash_verifier_1 = require("./hash-verifier");
13
16
  /**
14
17
  * Get allowlisted domains from provider data
15
18
  * Only URLs from these domains will be considered safe.
16
19
  */
17
20
  function getAllowedDomains() {
18
- const { providers } = (0, loader_1.loadProviders)();
21
+ const filePath = (0, path_1.join)(__dirname, '..', 'providers', 'emailproviders.json');
22
+ const integrity = (0, hash_verifier_1.verifyProvidersIntegrity)(filePath);
23
+ if (!integrity.isValid) {
24
+ return new Set();
25
+ }
26
+ if (cachedAllowedDomains && cachedAllowlistHash === integrity.actualHash) {
27
+ return cachedAllowedDomains;
28
+ }
29
+ const fileContent = (0, fs_1.readFileSync)(filePath, 'utf8');
30
+ const data = JSON.parse(fileContent);
31
+ const providers = data.providers;
19
32
  const allowedDomains = new Set();
20
33
  for (const provider of providers) {
21
34
  if (provider.loginUrl) {
22
35
  try {
23
36
  const url = new URL(provider.loginUrl);
24
- allowedDomains.add(url.hostname);
37
+ allowedDomains.add((0, idn_1.domainToPunycode)(url.hostname.toLowerCase()));
25
38
  }
26
39
  catch {
27
40
  // Skip invalid URLs
@@ -29,8 +42,12 @@ function getAllowedDomains() {
29
42
  }
30
43
  }
31
44
  }
45
+ cachedAllowedDomains = allowedDomains;
46
+ cachedAllowlistHash = integrity.actualHash;
32
47
  return allowedDomains;
33
48
  }
49
+ let cachedAllowedDomains = null;
50
+ let cachedAllowlistHash = null;
34
51
  /**
35
52
  * Suspicious URL patterns that should always be rejected
36
53
  */
@@ -188,6 +205,18 @@ function validateAllProviderUrls(providers) {
188
205
  validation: validateEmailProviderUrl(provider.loginUrl)
189
206
  });
190
207
  }
208
+ else {
209
+ // Providers without URLs are counted but marked as invalid for audit purposes
210
+ // (they don't affect security level, but are tracked for completeness)
211
+ results.push({
212
+ provider: provider.companyProvider || 'Unknown',
213
+ url: provider.loginUrl || '',
214
+ validation: {
215
+ isValid: false,
216
+ reason: provider.loginUrl === '' ? 'Empty URL provided' : 'No URL provided'
217
+ }
218
+ });
219
+ }
191
220
  }
192
221
  return results;
193
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikkelscheike/email-provider-links",
3
- "version": "4.0.11",
3
+ "version": "5.1.0",
4
4
  "description": "TypeScript library for email provider detection with 130 providers (218 domains), concurrent DNS resolution, optimized performance, 94.65% test coverage, and enterprise security for login and password reset flows",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,6 +17,7 @@
17
17
  "test": "jest",
18
18
  "test:watch": "jest --watch",
19
19
  "test:coverage": "jest --coverage",
20
+ "test:live-dns": "RUN_LIVE_DNS=1 jest",
20
21
  "prepublishOnly": "npm run verify-hashes && npm run build",
21
22
  "dev": "tsx src/index.ts",
22
23
  "sync-versions": "node scripts/sync-versions.js",
@@ -74,7 +75,7 @@
74
75
  "@semantic-release/npm": "^13.1.2",
75
76
  "@semantic-release/release-notes-generator": "^14.1.0",
76
77
  "@types/jest": "^30.0.0",
77
- "@types/node": "^24.10.1",
78
+ "@types/node": "^25.0.3",
78
79
  "conventional-changelog-conventionalcommits": "^9.1.0",
79
80
  "jest": "^30.2.0",
80
81
  "semantic-release": "^25.0.2",
package/dist/loader.d.ts DELETED
@@ -1,45 +0,0 @@
1
- /**
2
- * Provider Data Loader
3
- *
4
- * Handles loading email provider data with performance optimizations.
5
- */
6
- import { EmailProvider } from './api';
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 {};
45
- //# sourceMappingURL=loader.d.ts.map
package/dist/loader.js DELETED
@@ -1,163 +0,0 @@
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
- if (!compressedProvider.type) {
33
- console.warn(`Missing type for provider ${compressedProvider.id}`);
34
- }
35
- const provider = {
36
- companyProvider: compressedProvider.companyProvider,
37
- loginUrl: compressedProvider.loginUrl || null,
38
- domains: compressedProvider.domains || [],
39
- type: compressedProvider.type,
40
- alias: compressedProvider.alias
41
- };
42
- // Include DNS detection patterns for business email services and proxy services
43
- const needsCustomDomainDetection = compressedProvider.type === 'custom_provider' ||
44
- compressedProvider.type === 'proxy_service';
45
- if (needsCustomDomainDetection && (compressedProvider.mx?.length || compressedProvider.txt?.length)) {
46
- provider.customDomainDetection = {};
47
- if (compressedProvider.mx?.length) {
48
- provider.customDomainDetection.mxPatterns = compressedProvider.mx;
49
- }
50
- if (compressedProvider.txt?.length) {
51
- // Decompress TXT patterns
52
- provider.customDomainDetection.txtPatterns = compressedProvider.txt.map(schema_1.decompressTxtPattern);
53
- }
54
- }
55
- return provider;
56
- }
57
- /**
58
- * Internal provider data loader with configuration
59
- */
60
- function loadProvidersInternal(config = {}) {
61
- const mergedConfig = { ...DEFAULT_CONFIG, ...config };
62
- const startTime = Date.now();
63
- // Return cached data if available
64
- if (cachedProviders && !mergedConfig.debug) {
65
- return {
66
- providers: cachedProviders,
67
- stats: loadingStats
68
- };
69
- }
70
- try {
71
- // Determine file path
72
- const basePath = (0, path_1.join)(__dirname, '..', 'providers');
73
- const dataPath = mergedConfig.path || (0, path_1.join)(basePath, 'emailproviders.json');
74
- if (mergedConfig.debug)
75
- console.log('🔄 Loading provider data...');
76
- const content = (0, fs_1.readFileSync)(dataPath, 'utf8');
77
- const data = JSON.parse(content);
78
- // Validate format
79
- if (!data.version || !data.providers || !Array.isArray(data.providers)) {
80
- throw new Error('Invalid provider data format');
81
- }
82
- const providers = data.providers.map(convertProviderToEmailProvider);
83
- const fileSize = content.length;
84
- if (mergedConfig.debug) {
85
- console.log(`✅ Loaded ${providers.length} providers`);
86
- console.log(`📊 File size: ${(fileSize / 1024).toFixed(1)} KB`);
87
- }
88
- const loadTime = Date.now() - startTime;
89
- const domainCount = providers.reduce((sum, p) => sum + p.domains.length, 0);
90
- // Cache the results
91
- cachedProviders = providers;
92
- loadingStats = {
93
- fileSize,
94
- loadTime,
95
- providerCount: providers.length,
96
- domainCount
97
- };
98
- if (mergedConfig.debug) {
99
- console.log(`⚡ Loading completed in ${loadTime}ms`);
100
- console.log(`📊 Stats: ${providers.length} providers, ${domainCount} domains`);
101
- }
102
- if (process.env.NODE_ENV === 'development') {
103
- const memoryUsageInMB = process.memoryUsage().heapUsed / 1024 / 1024;
104
- console.log(`🚀 Current memory usage: ${memoryUsageInMB.toFixed(2)} MB`);
105
- }
106
- return {
107
- providers,
108
- stats: loadingStats
109
- };
110
- }
111
- catch (error) {
112
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
113
- throw new Error(`Failed to load provider data: ${errorMessage}`);
114
- }
115
- }
116
- /**
117
- * Build optimized domain-to-provider lookup map
118
- */
119
- function buildDomainMap(providers) {
120
- if (cachedDomainMap) {
121
- return cachedDomainMap;
122
- }
123
- const domainMap = new Map();
124
- for (const provider of providers) {
125
- for (const domain of provider.domains) {
126
- domainMap.set(domain.toLowerCase(), provider);
127
- }
128
- }
129
- cachedDomainMap = domainMap;
130
- return domainMap;
131
- }
132
- /**
133
- * Clear all caches (useful for testing or hot reloading)
134
- */
135
- function clearCache() {
136
- cachedProviders = null;
137
- cachedDomainMap = null;
138
- loadingStats = null;
139
- }
140
- /**
141
- * Get loading statistics from the last load operation
142
- */
143
- function getLoadingStats() {
144
- return loadingStats;
145
- }
146
- /**
147
- * Load all providers with optimized domain map for production
148
- */
149
- function loadProviders() {
150
- const { providers, stats } = loadProvidersInternal({ debug: false });
151
- const domainMap = buildDomainMap(providers);
152
- return { providers, domainMap, stats };
153
- }
154
- /**
155
- * Load providers with debug information
156
- */
157
- function loadProvidersDebug() {
158
- clearCache(); // Always reload in debug mode
159
- const { providers, stats } = loadProvidersInternal({ debug: true });
160
- const domainMap = buildDomainMap(providers);
161
- return { providers, domainMap, stats };
162
- }
163
- //# sourceMappingURL=loader.js.map