linkedin-secret-sauce 0.12.1 → 0.12.3

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.
Files changed (184) hide show
  1. package/README.md +339 -31
  2. package/dist/cosiall-client.d.ts +1 -1
  3. package/dist/cosiall-client.js +1 -1
  4. package/dist/enrichment/index.d.ts +23 -2
  5. package/dist/enrichment/index.js +38 -22
  6. package/dist/enrichment/matching.d.ts +16 -2
  7. package/dist/enrichment/matching.js +387 -65
  8. package/dist/enrichment/providers/bounceban.d.ts +82 -0
  9. package/dist/enrichment/providers/bounceban.js +447 -0
  10. package/dist/enrichment/providers/bouncer.d.ts +1 -1
  11. package/dist/enrichment/providers/bouncer.js +19 -21
  12. package/dist/enrichment/providers/construct.d.ts +1 -1
  13. package/dist/enrichment/providers/construct.js +22 -38
  14. package/dist/enrichment/providers/cosiall.d.ts +1 -1
  15. package/dist/enrichment/providers/cosiall.js +3 -4
  16. package/dist/enrichment/providers/dropcontact.d.ts +15 -9
  17. package/dist/enrichment/providers/dropcontact.js +188 -19
  18. package/dist/enrichment/providers/hunter.d.ts +8 -1
  19. package/dist/enrichment/providers/hunter.js +52 -28
  20. package/dist/enrichment/providers/index.d.ts +2 -0
  21. package/dist/enrichment/providers/index.js +10 -1
  22. package/dist/enrichment/providers/ldd.d.ts +1 -10
  23. package/dist/enrichment/providers/ldd.js +20 -97
  24. package/dist/enrichment/providers/smartprospect.js +28 -48
  25. package/dist/enrichment/providers/snovio.d.ts +1 -1
  26. package/dist/enrichment/providers/snovio.js +29 -31
  27. package/dist/enrichment/providers/trykitt.d.ts +63 -0
  28. package/dist/enrichment/providers/trykitt.js +210 -0
  29. package/dist/enrichment/types.d.ts +220 -7
  30. package/dist/enrichment/types.js +16 -8
  31. package/dist/enrichment/utils/candidate-parser.d.ts +107 -0
  32. package/dist/enrichment/utils/candidate-parser.js +173 -0
  33. package/dist/enrichment/utils/noop-provider.d.ts +39 -0
  34. package/dist/enrichment/utils/noop-provider.js +37 -0
  35. package/dist/enrichment/utils/rate-limiter.d.ts +103 -0
  36. package/dist/enrichment/utils/rate-limiter.js +204 -0
  37. package/dist/enrichment/utils/validation.d.ts +75 -3
  38. package/dist/enrichment/utils/validation.js +164 -11
  39. package/dist/linkedin-api.d.ts +40 -1
  40. package/dist/linkedin-api.js +160 -27
  41. package/dist/types.d.ts +50 -1
  42. package/dist/utils/lru-cache.d.ts +105 -0
  43. package/dist/utils/lru-cache.js +175 -0
  44. package/docs/COSIALL_PROFILE_EMAILS.md +342 -0
  45. package/docs/ENRICHMENT.md +622 -0
  46. package/docs/INTEGRATION.md +405 -0
  47. package/docs/PLAYGROUND.md +558 -0
  48. package/docs/SALES_SEARCH.md +171 -0
  49. package/docs/api/.nojekyll +1 -0
  50. package/docs/api/assets/hierarchy.js +1 -0
  51. package/docs/api/assets/highlight.css +92 -0
  52. package/docs/api/assets/icons.js +18 -0
  53. package/docs/api/assets/icons.svg +1 -0
  54. package/docs/api/assets/main.js +60 -0
  55. package/docs/api/assets/navigation.js +1 -0
  56. package/docs/api/assets/search.js +1 -0
  57. package/docs/api/assets/style.css +1633 -0
  58. package/docs/api/classes/LinkedInClientError.html +37 -0
  59. package/docs/api/functions/_testGetAccountCookies.html +4 -0
  60. package/docs/api/functions/_testGetAccountEntry.html +4 -0
  61. package/docs/api/functions/_testGetAllAccountIds.html +3 -0
  62. package/docs/api/functions/_testGetPoolState.html +3 -0
  63. package/docs/api/functions/adminResetAccount.html +1 -0
  64. package/docs/api/functions/adminSetCooldown.html +1 -0
  65. package/docs/api/functions/buildCookieHeader.html +1 -0
  66. package/docs/api/functions/clearAllSmartLeadTokens.html +2 -0
  67. package/docs/api/functions/clearRequestHistory.html +1 -0
  68. package/docs/api/functions/clearSessionAccount.html +1 -0
  69. package/docs/api/functions/clearSmartLeadToken.html +2 -0
  70. package/docs/api/functions/createEnrichmentClient.html +8 -0
  71. package/docs/api/functions/extractCsrfToken.html +1 -0
  72. package/docs/api/functions/extractLinkedInHandle.html +7 -0
  73. package/docs/api/functions/fetchCookiesFromCosiall.html +14 -0
  74. package/docs/api/functions/fetchProfileEmailsFromCosiall.html +18 -0
  75. package/docs/api/functions/forceRefreshCookies.html +1 -0
  76. package/docs/api/functions/getAccountForSession.html +1 -0
  77. package/docs/api/functions/getAccountsSummary.html +1 -0
  78. package/docs/api/functions/getCompaniesBatch.html +5 -0
  79. package/docs/api/functions/getCompanyById.html +9 -0
  80. package/docs/api/functions/getCompanyByUrl.html +1 -0
  81. package/docs/api/functions/getConfig.html +1 -0
  82. package/docs/api/functions/getCookiePoolHealth.html +1 -0
  83. package/docs/api/functions/getProfileByUrn.html +17 -0
  84. package/docs/api/functions/getProfileByVanity.html +10 -0
  85. package/docs/api/functions/getProfilesBatch.html +1 -0
  86. package/docs/api/functions/getRequestHistory.html +1 -0
  87. package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -0
  88. package/docs/api/functions/getSalesNavigatorProfileFull.html +16 -0
  89. package/docs/api/functions/getSmartLeadToken.html +1 -0
  90. package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -0
  91. package/docs/api/functions/getSmartLeadUser.html +2 -0
  92. package/docs/api/functions/getSnapshot.html +1 -0
  93. package/docs/api/functions/getYearsAtCompanyOptions.html +2 -0
  94. package/docs/api/functions/getYearsInPositionOptions.html +2 -0
  95. package/docs/api/functions/getYearsOfExperienceOptions.html +2 -0
  96. package/docs/api/functions/incrementMetric.html +1 -0
  97. package/docs/api/functions/initializeCookiePool.html +1 -0
  98. package/docs/api/functions/initializeLinkedInClient.html +1 -0
  99. package/docs/api/functions/isBusinessEmail.html +4 -0
  100. package/docs/api/functions/isDisposableDomain.html +4 -0
  101. package/docs/api/functions/isDisposableEmail.html +4 -0
  102. package/docs/api/functions/isPersonalDomain.html +4 -0
  103. package/docs/api/functions/isPersonalEmail.html +4 -0
  104. package/docs/api/functions/isRoleAccount.html +4 -0
  105. package/docs/api/functions/isValidEmailSyntax.html +4 -0
  106. package/docs/api/functions/parseFullProfile.html +15 -0
  107. package/docs/api/functions/parseSalesSearchResults.html +1 -0
  108. package/docs/api/functions/reportAccountFailure.html +1 -0
  109. package/docs/api/functions/reportAccountSuccess.html +1 -0
  110. package/docs/api/functions/resolveCompanyUniversalName.html +1 -0
  111. package/docs/api/functions/searchSalesLeads.html +16 -0
  112. package/docs/api/functions/selectAccountForRequest.html +1 -0
  113. package/docs/api/functions/setAccountForSession.html +1 -0
  114. package/docs/api/functions/typeahead.html +1 -0
  115. package/docs/api/functions/verifyEmailMx.html +1 -0
  116. package/docs/api/hierarchy.html +1 -0
  117. package/docs/api/index.html +12 -0
  118. package/docs/api/interfaces/AccountCookies.html +4 -0
  119. package/docs/api/interfaces/BatchEnrichmentOptions.html +14 -0
  120. package/docs/api/interfaces/CacheAdapter.html +6 -0
  121. package/docs/api/interfaces/CanonicalEmail.html +14 -0
  122. package/docs/api/interfaces/Company.html +17 -0
  123. package/docs/api/interfaces/ConstructConfig.html +8 -0
  124. package/docs/api/interfaces/CosiallProfileEmailsResponse.html +11 -0
  125. package/docs/api/interfaces/DropcontactConfig.html +3 -0
  126. package/docs/api/interfaces/EnrichmentCandidate.html +34 -0
  127. package/docs/api/interfaces/EnrichmentClient.html +10 -0
  128. package/docs/api/interfaces/EnrichmentClientConfig.html +12 -0
  129. package/docs/api/interfaces/EnrichmentLogger.html +6 -0
  130. package/docs/api/interfaces/EnrichmentOptions.html +10 -0
  131. package/docs/api/interfaces/HunterConfig.html +3 -0
  132. package/docs/api/interfaces/LddConfig.html +4 -0
  133. package/docs/api/interfaces/LddProfileData.html +6 -0
  134. package/docs/api/interfaces/LinkedInClientConfig.html +20 -0
  135. package/docs/api/interfaces/LinkedInCookie.html +9 -0
  136. package/docs/api/interfaces/LinkedInPosition.html +14 -0
  137. package/docs/api/interfaces/LinkedInProfile.html +21 -0
  138. package/docs/api/interfaces/LinkedInSpotlightBadge.html +5 -0
  139. package/docs/api/interfaces/LinkedInTenure.html +3 -0
  140. package/docs/api/interfaces/Metrics.html +22 -0
  141. package/docs/api/interfaces/MetricsSnapshot.html +23 -0
  142. package/docs/api/interfaces/ProfileEducation.html +8 -0
  143. package/docs/api/interfaces/ProfileEmailsLookupOptions.html +9 -0
  144. package/docs/api/interfaces/ProfilePosition.html +12 -0
  145. package/docs/api/interfaces/ProfileSkill.html +3 -0
  146. package/docs/api/interfaces/ProviderResult.html +11 -0
  147. package/docs/api/interfaces/ProvidersConfig.html +17 -0
  148. package/docs/api/interfaces/RequestHistoryEntry.html +8 -0
  149. package/docs/api/interfaces/SalesLeadSearchResult.html +31 -0
  150. package/docs/api/interfaces/SalesNavigatorContactInfo.html +5 -0
  151. package/docs/api/interfaces/SalesNavigatorPosition.html +11 -0
  152. package/docs/api/interfaces/SalesNavigatorProfile.html +9 -0
  153. package/docs/api/interfaces/SalesNavigatorProfileFull.html +24 -0
  154. package/docs/api/interfaces/SearchSalesResult.html +5 -0
  155. package/docs/api/interfaces/SmartLeadAuthConfig.html +6 -0
  156. package/docs/api/interfaces/SmartLeadCredentials.html +3 -0
  157. package/docs/api/interfaces/SmartLeadLoginResponse.html +3 -0
  158. package/docs/api/interfaces/SmartLeadUser.html +8 -0
  159. package/docs/api/interfaces/SmartProspectConfig.html +19 -0
  160. package/docs/api/interfaces/SmartProspectContact.html +24 -0
  161. package/docs/api/interfaces/SmartProspectSearchFilters.html +42 -0
  162. package/docs/api/interfaces/TypeaheadItem.html +3 -0
  163. package/docs/api/interfaces/TypeaheadResult.html +3 -0
  164. package/docs/api/interfaces/VerificationResult.html +16 -0
  165. package/docs/api/types/CostCallback.html +2 -0
  166. package/docs/api/types/Geo.html +4 -0
  167. package/docs/api/types/LddApiResponse.html +1 -0
  168. package/docs/api/types/ProviderFunc.html +2 -0
  169. package/docs/api/types/ProviderName.html +2 -0
  170. package/docs/api/types/SalesSearchFilters.html +7 -0
  171. package/docs/api/types/TypeaheadType.html +1 -0
  172. package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -0
  173. package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +20 -0
  174. package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -0
  175. package/docs/api/variables/FUNCTION_OPTIONS.html +1 -0
  176. package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -0
  177. package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -0
  178. package/docs/api/variables/PERSONAL_DOMAINS.html +2 -0
  179. package/docs/api/variables/PROVIDER_COSTS.html +15 -0
  180. package/docs/api/variables/REGION_OPTIONS.html +1 -0
  181. package/docs/api/variables/SENIORITY_OPTIONS.html +3 -0
  182. package/docs/api/variables/YEARS_OPTIONS.html +1 -0
  183. package/docs/index.html +98 -0
  184. package/package.json +16 -5
@@ -0,0 +1,447 @@
1
+ "use strict";
2
+ /**
3
+ * BounceBan Email Verification Provider
4
+ *
5
+ * Specializes in catch-all email verification with 85-95% accuracy
6
+ * using proprietary algorithms WITHOUT sending actual emails.
7
+ *
8
+ * Features:
9
+ * - Catch-all domain verification (industry-leading)
10
+ * - DeepVerify mode for pattern-guessed emails (+15-25% accuracy)
11
+ * - Waterfall endpoint with FREE 30-min retry window
12
+ * - Single email verification is FREE
13
+ * - GDPR compliant (no emails sent)
14
+ *
15
+ * API Endpoints:
16
+ * - GET /v1/verify/single - Standard single verification
17
+ * - GET https://api-waterfall.bounceban.com/v1/verify/single - Waterfall (recommended)
18
+ * - POST /v1/verify/bulk - Bulk verification
19
+ *
20
+ * Result values:
21
+ * - deliverable: Email verified (score 70-100)
22
+ * - risky: May work, often catch-all (score 30-70)
23
+ * - undeliverable: Email doesn't exist (score 0-30)
24
+ * - unknown: Could not determine
25
+ *
26
+ * @see https://bounceban.com
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.createBounceBanProvider = createBounceBanProvider;
30
+ exports.verifyEmailWithBounceBan = verifyEmailWithBounceBan;
31
+ exports.verifyEmailsBatchWithBounceBan = verifyEmailsBatchWithBounceBan;
32
+ exports.checkCatchAllWithBounceBan = checkCatchAllWithBounceBan;
33
+ const http_retry_1 = require("../utils/http-retry");
34
+ const noop_provider_1 = require("../utils/noop-provider");
35
+ const DEFAULT_API_URL = "https://api.bounceban.com";
36
+ const DEFAULT_WATERFALL_API_URL = "https://api-waterfall.bounceban.com";
37
+ const DEFAULT_TIMEOUT_MS = 30000;
38
+ const DEFAULT_WATERFALL_TIMEOUT_MS = 80000;
39
+ /**
40
+ * Map BounceBan result to confidence score
41
+ */
42
+ function resultToConfidence(result, score, isAcceptAll) {
43
+ // Use the score directly if provided (0-100)
44
+ if (score >= 0 && score <= 100) {
45
+ // Reduce confidence for catch-all domains
46
+ if (isAcceptAll && result === "deliverable") {
47
+ return Math.min(score, 75); // Cap at 75 for catch-all even if deliverable
48
+ }
49
+ return score;
50
+ }
51
+ // Fallback based on result type
52
+ switch (result) {
53
+ case "deliverable":
54
+ return isAcceptAll ? 70 : 95;
55
+ case "risky":
56
+ return isAcceptAll ? 50 : 60;
57
+ case "undeliverable":
58
+ return 10;
59
+ case "unknown":
60
+ default:
61
+ return 30;
62
+ }
63
+ }
64
+ /**
65
+ * Map BounceBan result to verified status
66
+ */
67
+ function resultToVerified(result) {
68
+ switch (result) {
69
+ case "deliverable":
70
+ return true;
71
+ case "undeliverable":
72
+ return false;
73
+ case "risky":
74
+ case "unknown":
75
+ default:
76
+ return undefined; // Can't determine
77
+ }
78
+ }
79
+ /**
80
+ * Extract emails to verify from candidate
81
+ * BounceBan is a VERIFICATION provider - it needs emails to verify
82
+ */
83
+ function extractEmailsToVerify(candidate) {
84
+ const emails = [];
85
+ // Check if candidate has pre-generated email patterns in metadata
86
+ const metadata = candidate._emailCandidates;
87
+ if (Array.isArray(metadata)) {
88
+ emails.push(...metadata.filter((e) => typeof e === "string"));
89
+ }
90
+ // Also check for a single email field
91
+ const singleEmail = candidate.email;
92
+ if (typeof singleEmail === "string" && singleEmail.includes("@")) {
93
+ emails.push(singleEmail);
94
+ }
95
+ return [...new Set(emails)]; // Deduplicate
96
+ }
97
+ /**
98
+ * Verify a single email via BounceBan standard endpoint
99
+ */
100
+ async function verifyEmailStandard(email, apiKey, apiUrl, timeoutMs, useDeepVerify) {
101
+ const params = new URLSearchParams({
102
+ api_key: apiKey,
103
+ email: email,
104
+ });
105
+ if (useDeepVerify) {
106
+ params.set("mode", "deepverify");
107
+ }
108
+ try {
109
+ const response = await (0, http_retry_1.getWithRetry)(`${apiUrl}/v1/verify/single?${params.toString()}`, undefined, {
110
+ retries: 1,
111
+ backoffMs: 500,
112
+ timeoutMs,
113
+ retryOnStatus: [429, 500, 502, 503, 504],
114
+ });
115
+ return response;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /**
122
+ * Verify a single email via BounceBan waterfall endpoint
123
+ *
124
+ * The waterfall endpoint is optimized for:
125
+ * - Single HTTP request (no polling needed)
126
+ * - FREE retries within 30-min window if timeout
127
+ * - Better catch-all handling
128
+ */
129
+ async function verifyEmailWaterfall(email, apiKey, waterfallApiUrl, timeoutMs, useDeepVerify) {
130
+ const params = new URLSearchParams({
131
+ api_key: apiKey,
132
+ email: email,
133
+ timeout: String(Math.floor(timeoutMs / 1000)), // Convert to seconds
134
+ });
135
+ if (useDeepVerify) {
136
+ params.set("mode", "deepverify");
137
+ }
138
+ try {
139
+ const response = await (0, http_retry_1.getWithRetry)(`${waterfallApiUrl}/v1/verify/single?${params.toString()}`, undefined, {
140
+ retries: 2, // Waterfall allows free retries within 30 mins
141
+ backoffMs: 5000, // Wait 5s between retries as recommended
142
+ timeoutMs: timeoutMs + 10000, // Add buffer for network
143
+ retryOnStatus: [408, 429, 500, 502, 503, 504], // Include 408 timeout
144
+ });
145
+ return response;
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ /**
152
+ * Create the BounceBan verification provider
153
+ *
154
+ * This provider specializes in verifying emails on catch-all domains
155
+ * where traditional SMTP verification fails.
156
+ *
157
+ * Best used after construct provider generates email patterns.
158
+ */
159
+ function createBounceBanProvider(config) {
160
+ const { apiKey, apiUrl = DEFAULT_API_URL, waterfallApiUrl = DEFAULT_WATERFALL_API_URL, timeoutMs, useDeepVerify = false, useWaterfall = true, } = config;
161
+ // Determine timeout based on endpoint
162
+ const effectiveTimeout = timeoutMs ??
163
+ (useWaterfall ? DEFAULT_WATERFALL_TIMEOUT_MS : DEFAULT_TIMEOUT_MS);
164
+ if (!apiKey) {
165
+ return (0, noop_provider_1.createNoOpProvider)("bounceban");
166
+ }
167
+ /**
168
+ * Verify emails for a candidate
169
+ */
170
+ async function verifyEmails(candidate) {
171
+ const emailsToVerify = extractEmailsToVerify(candidate);
172
+ // If no emails to verify, return null
173
+ if (emailsToVerify.length === 0) {
174
+ return null;
175
+ }
176
+ const verifiedEmails = [];
177
+ // Verify each email
178
+ for (const email of emailsToVerify) {
179
+ // Add small delay between verifications to respect rate limits
180
+ if (verifiedEmails.length > 0) {
181
+ await (0, http_retry_1.delay)(200);
182
+ }
183
+ const result = useWaterfall
184
+ ? await verifyEmailWaterfall(email, apiKey, waterfallApiUrl, effectiveTimeout, useDeepVerify)
185
+ : await verifyEmailStandard(email, apiKey, apiUrl, effectiveTimeout, useDeepVerify);
186
+ if (!result || result.status !== "success") {
187
+ continue;
188
+ }
189
+ const confidence = resultToConfidence(result.result, result.score, result.is_accept_all);
190
+ const verified = resultToVerified(result.result);
191
+ // Only include potentially deliverable emails
192
+ if (result.result === "deliverable" || result.result === "risky") {
193
+ verifiedEmails.push({
194
+ email: result.email,
195
+ verified,
196
+ confidence,
197
+ isCatchAll: result.is_accept_all,
198
+ metadata: {
199
+ bounceBanResult: result.result,
200
+ bounceBanScore: result.score,
201
+ smtpProvider: result.smtp_provider,
202
+ isDisposable: result.is_disposable,
203
+ isRole: result.is_role,
204
+ isFree: result.is_free,
205
+ mxRecords: result.mx_records,
206
+ mode: result.mode,
207
+ },
208
+ });
209
+ }
210
+ }
211
+ if (verifiedEmails.length === 0) {
212
+ return null;
213
+ }
214
+ // Sort by confidence (highest first)
215
+ verifiedEmails.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0));
216
+ return { emails: verifiedEmails };
217
+ }
218
+ // Mark provider name for orchestrator
219
+ verifyEmails.__name = "bounceban";
220
+ return verifyEmails;
221
+ }
222
+ /**
223
+ * Standalone function to verify a single email via BounceBan
224
+ *
225
+ * Uses the waterfall endpoint by default for best catch-all handling.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * const result = await verifyEmailWithBounceBan('john@catchall-domain.com', {
230
+ * apiKey: process.env.BOUNCEBAN_API_KEY,
231
+ * });
232
+ * console.log(result.result); // "deliverable" | "risky" | "undeliverable" | "unknown"
233
+ * console.log(result.score); // 0-100
234
+ * console.log(result.is_accept_all); // true (catch-all domain)
235
+ * ```
236
+ */
237
+ async function verifyEmailWithBounceBan(email, config) {
238
+ const { apiKey, waterfallApiUrl = DEFAULT_WATERFALL_API_URL, apiUrl = DEFAULT_API_URL, timeoutMs, useDeepVerify = false, useWaterfall = true, } = config;
239
+ if (!apiKey) {
240
+ return null;
241
+ }
242
+ const effectiveTimeout = timeoutMs ??
243
+ (useWaterfall ? DEFAULT_WATERFALL_TIMEOUT_MS : DEFAULT_TIMEOUT_MS);
244
+ return useWaterfall
245
+ ? verifyEmailWaterfall(email, apiKey, waterfallApiUrl, effectiveTimeout, useDeepVerify)
246
+ : verifyEmailStandard(email, apiKey, apiUrl, effectiveTimeout, useDeepVerify);
247
+ }
248
+ /**
249
+ * Submit emails for bulk verification via BounceBan
250
+ *
251
+ * @returns Task ID for polling status
252
+ */
253
+ async function submitBulkVerification(emails, apiKey, apiUrl, useDeepVerify) {
254
+ try {
255
+ const response = await fetch(`${apiUrl}/v1/verify/bulk`, {
256
+ method: "POST",
257
+ headers: {
258
+ "Content-Type": "application/json",
259
+ },
260
+ body: JSON.stringify({
261
+ api_key: apiKey,
262
+ emails: emails,
263
+ mode: useDeepVerify ? "deepverify" : "regular",
264
+ }),
265
+ });
266
+ if (!response.ok) {
267
+ return null;
268
+ }
269
+ const data = (await response.json());
270
+ return data.id || null;
271
+ }
272
+ catch {
273
+ return null;
274
+ }
275
+ }
276
+ /**
277
+ * Check bulk verification task status
278
+ */
279
+ async function checkBulkStatus(taskId, apiKey, apiUrl) {
280
+ try {
281
+ const params = new URLSearchParams({
282
+ api_key: apiKey,
283
+ id: taskId,
284
+ });
285
+ const response = await fetch(`${apiUrl}/v1/verify/bulk/status?${params.toString()}`);
286
+ if (!response.ok) {
287
+ return null;
288
+ }
289
+ return (await response.json());
290
+ }
291
+ catch {
292
+ return null;
293
+ }
294
+ }
295
+ /**
296
+ * Fetch bulk verification results (paginated)
297
+ */
298
+ async function fetchBulkResults(taskId, apiKey, apiUrl, page = 1, perPage = 100) {
299
+ try {
300
+ const response = await fetch(`${apiUrl}/v1/verify/bulk/dump`, {
301
+ method: "POST",
302
+ headers: {
303
+ "Content-Type": "application/json",
304
+ },
305
+ body: JSON.stringify({
306
+ api_key: apiKey,
307
+ id: taskId,
308
+ page: page,
309
+ per_page: perPage,
310
+ }),
311
+ });
312
+ if (!response.ok) {
313
+ return null;
314
+ }
315
+ return (await response.json());
316
+ }
317
+ catch {
318
+ return null;
319
+ }
320
+ }
321
+ /**
322
+ * Verify multiple emails in batch via BounceBan Bulk API
323
+ *
324
+ * More cost-effective for large volumes ($0.003/email vs single free).
325
+ * Uses async bulk API with polling for results.
326
+ *
327
+ * For small batches (< 5 emails), falls back to sequential single verification.
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const results = await verifyEmailsBatchWithBounceBan(
332
+ * ['john@example.com', 'jane@example.com'],
333
+ * { apiKey: process.env.BOUNCEBAN_API_KEY }
334
+ * );
335
+ * ```
336
+ */
337
+ async function verifyEmailsBatchWithBounceBan(emails, config) {
338
+ const results = new Map();
339
+ const { apiKey, apiUrl = DEFAULT_API_URL, useDeepVerify = false } = config;
340
+ if (!apiKey || emails.length === 0) {
341
+ return results;
342
+ }
343
+ // For small batches, use sequential single verification (it's free)
344
+ if (emails.length < 5) {
345
+ for (const email of emails) {
346
+ const result = await verifyEmailWithBounceBan(email, config);
347
+ results.set(email, result);
348
+ // Small delay between requests
349
+ if (emails.indexOf(email) < emails.length - 1) {
350
+ await (0, http_retry_1.delay)(200);
351
+ }
352
+ }
353
+ return results;
354
+ }
355
+ // For larger batches, use bulk API
356
+ const taskId = await submitBulkVerification(emails, apiKey, apiUrl, useDeepVerify);
357
+ if (!taskId) {
358
+ // Fallback to sequential if bulk submit fails
359
+ for (const email of emails) {
360
+ const result = await verifyEmailWithBounceBan(email, config);
361
+ results.set(email, result);
362
+ if (emails.indexOf(email) < emails.length - 1) {
363
+ await (0, http_retry_1.delay)(200);
364
+ }
365
+ }
366
+ return results;
367
+ }
368
+ // Poll for completion (max 5 minutes)
369
+ const maxPollTime = 5 * 60 * 1000;
370
+ const pollInterval = 2000;
371
+ const startTime = Date.now();
372
+ while (Date.now() - startTime < maxPollTime) {
373
+ await (0, http_retry_1.delay)(pollInterval);
374
+ const status = await checkBulkStatus(taskId, apiKey, apiUrl);
375
+ if (!status) {
376
+ continue;
377
+ }
378
+ if (status.status === "error") {
379
+ // Bulk failed, return empty results
380
+ return results;
381
+ }
382
+ if (status.status === "finished") {
383
+ // Fetch all results with pagination
384
+ let page = 1;
385
+ const perPage = 100;
386
+ let hasMore = true;
387
+ while (hasMore) {
388
+ const dumpResponse = await fetchBulkResults(taskId, apiKey, apiUrl, page, perPage);
389
+ if (!dumpResponse || dumpResponse.results.length === 0) {
390
+ hasMore = false;
391
+ break;
392
+ }
393
+ // Map results to our response format
394
+ for (const item of dumpResponse.results) {
395
+ const response = {
396
+ id: taskId,
397
+ status: "success",
398
+ email: item.email,
399
+ result: item.result,
400
+ score: item.score,
401
+ is_disposable: item.is_disposable,
402
+ is_accept_all: item.is_accept_all,
403
+ is_role: item.is_role,
404
+ is_free: item.is_free,
405
+ mx_records: item.mx_records,
406
+ smtp_provider: item.smtp_provider,
407
+ };
408
+ results.set(item.email, response);
409
+ }
410
+ // Check if there are more pages
411
+ if (dumpResponse.results.length < perPage) {
412
+ hasMore = false;
413
+ }
414
+ else {
415
+ page++;
416
+ }
417
+ }
418
+ return results;
419
+ }
420
+ // Still processing, continue polling
421
+ }
422
+ // Timeout - return whatever we have
423
+ return results;
424
+ }
425
+ /**
426
+ * Check if a domain is catch-all via BounceBan
427
+ *
428
+ * Verifies a random non-existent email to detect catch-all behavior.
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * const isCatchAll = await checkCatchAllWithBounceBan('example.com', {
433
+ * apiKey: process.env.BOUNCEBAN_API_KEY,
434
+ * });
435
+ * ```
436
+ */
437
+ async function checkCatchAllWithBounceBan(domain, config) {
438
+ // Generate a random email that almost certainly doesn't exist
439
+ const randomLocal = `catchall-test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
440
+ const testEmail = `${randomLocal}@${domain}`;
441
+ const result = await verifyEmailWithBounceBan(testEmail, config);
442
+ if (!result) {
443
+ return null; // Could not determine
444
+ }
445
+ // If the random email is "deliverable" or "risky", the domain is catch-all
446
+ return result.is_accept_all || result.result === "deliverable";
447
+ }
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * @see https://docs.usebouncer.com
15
15
  */
16
- import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, BouncerConfig, BouncerVerifyResponse } from '../types';
16
+ import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, BouncerConfig, BouncerVerifyResponse } from "../types";
17
17
  /**
18
18
  * Create the Bouncer verification provider
19
19
  *
@@ -19,14 +19,15 @@ exports.createBouncerProvider = createBouncerProvider;
19
19
  exports.verifyEmailWithBouncer = verifyEmailWithBouncer;
20
20
  exports.checkCatchAllDomain = checkCatchAllDomain;
21
21
  exports.verifyEmailsBatch = verifyEmailsBatch;
22
- const DEFAULT_API_URL = 'https://api.usebouncer.com/v1.1';
22
+ const noop_provider_1 = require("../utils/noop-provider");
23
+ const DEFAULT_API_URL = "https://api.usebouncer.com/v1.1";
23
24
  const DEFAULT_TIMEOUT_MS = 30000;
24
25
  /**
25
26
  * Map Bouncer status to confidence score
26
27
  */
27
28
  function statusToConfidence(status, response) {
28
29
  switch (status) {
29
- case 'deliverable':
30
+ case "deliverable":
30
31
  // High confidence - email verified as deliverable
31
32
  // Reduce slightly if catch-all or high toxicity
32
33
  let score = 95;
@@ -37,13 +38,13 @@ function statusToConfidence(status, response) {
37
38
  if (response.role)
38
39
  score -= 5; // Role accounts slightly lower
39
40
  return Math.max(50, score);
40
- case 'risky':
41
+ case "risky":
41
42
  // Medium confidence - risky but might work
42
43
  return response.acceptAll ? 40 : 50;
43
- case 'undeliverable':
44
+ case "undeliverable":
44
45
  // Email does not exist
45
46
  return 0;
46
- case 'unknown':
47
+ case "unknown":
47
48
  // Could not verify
48
49
  return 30;
49
50
  default:
@@ -59,7 +60,7 @@ function extractEmailsToVerify(candidate) {
59
60
  // Check if candidate has pre-generated email patterns in metadata
60
61
  const metadata = candidate._emailCandidates;
61
62
  if (Array.isArray(metadata)) {
62
- emails.push(...metadata.filter((e) => typeof e === 'string'));
63
+ emails.push(...metadata.filter((e) => typeof e === "string"));
63
64
  }
64
65
  return emails;
65
66
  }
@@ -72,10 +73,10 @@ async function verifyEmail(email, apiKey, apiUrl, timeoutMs) {
72
73
  try {
73
74
  const url = `${apiUrl}/email/verify?email=${encodeURIComponent(email)}`;
74
75
  const response = await fetch(url, {
75
- method: 'GET',
76
+ method: "GET",
76
77
  headers: {
77
- 'x-api-key': apiKey,
78
- 'Accept': 'application/json',
78
+ "x-api-key": apiKey,
79
+ Accept: "application/json",
79
80
  },
80
81
  signal: controller.signal,
81
82
  });
@@ -83,7 +84,7 @@ async function verifyEmail(email, apiKey, apiUrl, timeoutMs) {
83
84
  // All HTTP errors return null - let caller decide how to handle
84
85
  return null;
85
86
  }
86
- const data = await response.json();
87
+ const data = (await response.json());
87
88
  return data;
88
89
  }
89
90
  catch {
@@ -106,12 +107,9 @@ async function verifyEmail(email, apiKey, apiUrl, timeoutMs) {
106
107
  * 3. Only verified emails are returned
107
108
  */
108
109
  function createBouncerProvider(config) {
109
- const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
110
+ const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
110
111
  if (!apiKey) {
111
- // Return no-op provider if not configured
112
- const noop = async () => null;
113
- noop.__name = 'bouncer';
114
- return noop;
112
+ return (0, noop_provider_1.createNoOpProvider)("bouncer");
115
113
  }
116
114
  async function verifyEmails(candidate) {
117
115
  const emailsToVerify = extractEmailsToVerify(candidate);
@@ -128,10 +126,10 @@ function createBouncerProvider(config) {
128
126
  if (result) {
129
127
  const confidence = statusToConfidence(result.status, result);
130
128
  // Only include emails that are potentially deliverable
131
- if (result.status === 'deliverable' || result.status === 'risky') {
129
+ if (result.status === "deliverable" || result.status === "risky") {
132
130
  verifiedEmails.push({
133
131
  email: result.email,
134
- verified: result.status === 'deliverable',
132
+ verified: result.status === "deliverable",
135
133
  confidence,
136
134
  isCatchAll: result.acceptAll,
137
135
  metadata: {
@@ -159,7 +157,7 @@ function createBouncerProvider(config) {
159
157
  return { emails: verifiedEmails };
160
158
  }
161
159
  // Mark provider name for orchestrator
162
- verifyEmails.__name = 'bouncer';
160
+ verifyEmails.__name = "bouncer";
163
161
  return verifyEmails;
164
162
  }
165
163
  /**
@@ -176,7 +174,7 @@ function createBouncerProvider(config) {
176
174
  * ```
177
175
  */
178
176
  async function verifyEmailWithBouncer(email, config) {
179
- const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
177
+ const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
180
178
  return verifyEmail(email, apiKey, apiUrl, timeoutMs);
181
179
  }
182
180
  /**
@@ -192,7 +190,7 @@ async function verifyEmailWithBouncer(email, config) {
192
190
  * ```
193
191
  */
194
192
  async function checkCatchAllDomain(domain, config) {
195
- const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
193
+ const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
196
194
  // Generate a random email that almost certainly doesn't exist
197
195
  const randomLocal = `bounce-test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
198
196
  const testEmail = `${randomLocal}@${domain}`;
@@ -215,7 +213,7 @@ async function checkCatchAllDomain(domain, config) {
215
213
  * ```
216
214
  */
217
215
  async function verifyEmailsBatch(emails, config) {
218
- const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
216
+ const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
219
217
  const results = new Map();
220
218
  // Verify in parallel with concurrency limit
221
219
  const CONCURRENCY = 5;
@@ -4,7 +4,7 @@
4
4
  * Generates email pattern candidates based on name + domain, then verifies via MX check.
5
5
  * This is a FREE provider that doesn't require any API keys.
6
6
  */
7
- import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, ConstructConfig } from '../types';
7
+ import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, ConstructConfig } from "../types";
8
8
  /**
9
9
  * Create the construct provider function
10
10
  */