linkedin-secret-sauce 0.10.1 → 0.11.1
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 +512 -232
- package/dist/enrichment/auth/smartlead-auth.d.ts +3 -3
- package/dist/enrichment/auth/smartlead-auth.js +25 -25
- package/dist/enrichment/index.d.ts +6 -4
- package/dist/enrichment/index.js +45 -13
- package/dist/enrichment/matching.d.ts +8 -3
- package/dist/enrichment/matching.js +7 -5
- package/dist/enrichment/orchestrator.js +48 -9
- package/dist/enrichment/providers/bouncer.d.ts +67 -0
- package/dist/enrichment/providers/bouncer.js +233 -0
- package/dist/enrichment/providers/construct.js +72 -14
- package/dist/enrichment/providers/hunter.js +6 -60
- package/dist/enrichment/providers/index.d.ts +2 -1
- package/dist/enrichment/providers/index.js +11 -3
- package/dist/enrichment/providers/ldd.js +5 -47
- package/dist/enrichment/providers/smartprospect.js +9 -14
- package/dist/enrichment/providers/snovio.d.ts +58 -0
- package/dist/enrichment/providers/snovio.js +286 -0
- package/dist/enrichment/types.d.ts +133 -10
- package/dist/enrichment/types.js +28 -6
- package/dist/enrichment/utils/http-retry.d.ts +96 -0
- package/dist/enrichment/utils/http-retry.js +162 -0
- package/dist/enrichment/verification/index.d.ts +1 -1
- package/dist/enrichment/verification/index.js +3 -1
- package/dist/enrichment/verification/mx.d.ts +33 -0
- package/dist/enrichment/verification/mx.js +367 -7
- package/dist/index.d.ts +196 -6
- package/dist/index.js +159 -12
- package/dist/parsers/search-parser.js +7 -3
- package/package.json +10 -3
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bouncer.io Email Verification Provider
|
|
4
|
+
*
|
|
5
|
+
* Provides SMTP-level email verification with 99%+ accuracy.
|
|
6
|
+
* Best for verifying pattern-guessed emails on non-catch-all domains.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - SMTP verification (checks if mailbox exists)
|
|
10
|
+
* - Catch-all domain detection
|
|
11
|
+
* - Disposable email detection
|
|
12
|
+
* - Role account detection
|
|
13
|
+
* - Toxicity scoring (0-5)
|
|
14
|
+
*
|
|
15
|
+
* @see https://docs.usebouncer.com
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.createBouncerProvider = createBouncerProvider;
|
|
19
|
+
exports.verifyEmailWithBouncer = verifyEmailWithBouncer;
|
|
20
|
+
exports.checkCatchAllDomain = checkCatchAllDomain;
|
|
21
|
+
exports.verifyEmailsBatch = verifyEmailsBatch;
|
|
22
|
+
const DEFAULT_API_URL = 'https://api.usebouncer.com/v1.1';
|
|
23
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
24
|
+
/**
|
|
25
|
+
* Map Bouncer status to confidence score
|
|
26
|
+
*/
|
|
27
|
+
function statusToConfidence(status, response) {
|
|
28
|
+
switch (status) {
|
|
29
|
+
case 'deliverable':
|
|
30
|
+
// High confidence - email verified as deliverable
|
|
31
|
+
// Reduce slightly if catch-all or high toxicity
|
|
32
|
+
let score = 95;
|
|
33
|
+
if (response.acceptAll)
|
|
34
|
+
score -= 20; // Catch-all reduces confidence
|
|
35
|
+
if (response.toxicity && response.toxicity >= 3)
|
|
36
|
+
score -= 10;
|
|
37
|
+
if (response.role)
|
|
38
|
+
score -= 5; // Role accounts slightly lower
|
|
39
|
+
return Math.max(50, score);
|
|
40
|
+
case 'risky':
|
|
41
|
+
// Medium confidence - risky but might work
|
|
42
|
+
return response.acceptAll ? 40 : 50;
|
|
43
|
+
case 'undeliverable':
|
|
44
|
+
// Email does not exist
|
|
45
|
+
return 0;
|
|
46
|
+
case 'unknown':
|
|
47
|
+
// Could not verify
|
|
48
|
+
return 30;
|
|
49
|
+
default:
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract email candidates from the enrichment candidate
|
|
55
|
+
* Bouncer is a VERIFICATION provider, so it needs emails to verify
|
|
56
|
+
*/
|
|
57
|
+
function extractEmailsToVerify(candidate) {
|
|
58
|
+
const emails = [];
|
|
59
|
+
// Check if candidate has pre-generated email patterns in metadata
|
|
60
|
+
const metadata = candidate._emailCandidates;
|
|
61
|
+
if (Array.isArray(metadata)) {
|
|
62
|
+
emails.push(...metadata.filter((e) => typeof e === 'string'));
|
|
63
|
+
}
|
|
64
|
+
return emails;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Verify a single email via Bouncer API
|
|
68
|
+
*/
|
|
69
|
+
async function verifyEmail(email, apiKey, apiUrl, timeoutMs) {
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
72
|
+
try {
|
|
73
|
+
const url = `${apiUrl}/email/verify?email=${encodeURIComponent(email)}`;
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: {
|
|
77
|
+
'x-api-key': apiKey,
|
|
78
|
+
'Accept': 'application/json',
|
|
79
|
+
},
|
|
80
|
+
signal: controller.signal,
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
// All HTTP errors return null - let caller decide how to handle
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
return data;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// All errors (network, timeout, etc.) return null
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
clearTimeout(timeoutId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create the Bouncer verification provider
|
|
99
|
+
*
|
|
100
|
+
* NOTE: This provider is a VERIFIER, not a FINDER. It verifies emails
|
|
101
|
+
* that were generated by the construct provider or passed in the candidate.
|
|
102
|
+
*
|
|
103
|
+
* Usage in the enrichment flow:
|
|
104
|
+
* 1. construct provider generates email patterns
|
|
105
|
+
* 2. bouncer provider verifies which patterns are deliverable
|
|
106
|
+
* 3. Only verified emails are returned
|
|
107
|
+
*/
|
|
108
|
+
function createBouncerProvider(config) {
|
|
109
|
+
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
|
|
110
|
+
if (!apiKey) {
|
|
111
|
+
// Return no-op provider if not configured
|
|
112
|
+
const noop = async () => null;
|
|
113
|
+
noop.__name = 'bouncer';
|
|
114
|
+
return noop;
|
|
115
|
+
}
|
|
116
|
+
async function verifyEmails(candidate) {
|
|
117
|
+
const emailsToVerify = extractEmailsToVerify(candidate);
|
|
118
|
+
// If no emails to verify, return null
|
|
119
|
+
// The orchestrator will need to pass emails from construct
|
|
120
|
+
if (emailsToVerify.length === 0) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const verifiedEmails = [];
|
|
124
|
+
// Verify each email candidate
|
|
125
|
+
for (const email of emailsToVerify) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await verifyEmail(email, apiKey, apiUrl, timeoutMs);
|
|
128
|
+
if (result) {
|
|
129
|
+
const confidence = statusToConfidence(result.status, result);
|
|
130
|
+
// Only include emails that are potentially deliverable
|
|
131
|
+
if (result.status === 'deliverable' || result.status === 'risky') {
|
|
132
|
+
verifiedEmails.push({
|
|
133
|
+
email: result.email,
|
|
134
|
+
verified: result.status === 'deliverable',
|
|
135
|
+
confidence,
|
|
136
|
+
isCatchAll: result.acceptAll,
|
|
137
|
+
metadata: {
|
|
138
|
+
bouncerStatus: result.status,
|
|
139
|
+
bouncerReason: result.reason,
|
|
140
|
+
toxicity: result.toxicity,
|
|
141
|
+
isDisposable: result.disposable,
|
|
142
|
+
isRole: result.role,
|
|
143
|
+
isFreeProvider: result.free,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Continue with other emails on error
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (verifiedEmails.length === 0) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
// Sort by confidence
|
|
158
|
+
verifiedEmails.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0));
|
|
159
|
+
return { emails: verifiedEmails };
|
|
160
|
+
}
|
|
161
|
+
// Mark provider name for orchestrator
|
|
162
|
+
verifyEmails.__name = 'bouncer';
|
|
163
|
+
return verifyEmails;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Standalone function to verify a single email via Bouncer
|
|
167
|
+
*
|
|
168
|
+
* Useful for ad-hoc verification outside the enrichment flow.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* const result = await verifyEmailWithBouncer('test@example.com', {
|
|
173
|
+
* apiKey: process.env.BOUNCER_API_KEY,
|
|
174
|
+
* });
|
|
175
|
+
* console.log(result.status); // 'deliverable' | 'undeliverable' | 'risky' | 'unknown'
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
async function verifyEmailWithBouncer(email, config) {
|
|
179
|
+
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
|
|
180
|
+
return verifyEmail(email, apiKey, apiUrl, timeoutMs);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if a domain is catch-all via Bouncer
|
|
184
|
+
*
|
|
185
|
+
* Sends a verification request for a random email and checks acceptAll flag.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const isCatchAll = await checkCatchAllDomain('example.com', {
|
|
190
|
+
* apiKey: process.env.BOUNCER_API_KEY,
|
|
191
|
+
* });
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
async function checkCatchAllDomain(domain, config) {
|
|
195
|
+
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
|
|
196
|
+
// Generate a random email that almost certainly doesn't exist
|
|
197
|
+
const randomLocal = `bounce-test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
198
|
+
const testEmail = `${randomLocal}@${domain}`;
|
|
199
|
+
const result = await verifyEmail(testEmail, apiKey, apiUrl, timeoutMs);
|
|
200
|
+
if (!result) {
|
|
201
|
+
return null; // Could not determine
|
|
202
|
+
}
|
|
203
|
+
// If acceptAll is true, the domain accepts all emails (catch-all)
|
|
204
|
+
return result.acceptAll;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Verify multiple emails in batch via Bouncer
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const results = await verifyEmailsBatch(
|
|
212
|
+
* ['john@example.com', 'jane@example.com'],
|
|
213
|
+
* { apiKey: process.env.BOUNCER_API_KEY }
|
|
214
|
+
* );
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
async function verifyEmailsBatch(emails, config) {
|
|
218
|
+
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
|
|
219
|
+
const results = new Map();
|
|
220
|
+
// Verify in parallel with concurrency limit
|
|
221
|
+
const CONCURRENCY = 5;
|
|
222
|
+
for (let i = 0; i < emails.length; i += CONCURRENCY) {
|
|
223
|
+
const batch = emails.slice(i, i + CONCURRENCY);
|
|
224
|
+
const batchResults = await Promise.all(batch.map(async (email) => {
|
|
225
|
+
const result = await verifyEmail(email, apiKey, apiUrl, timeoutMs);
|
|
226
|
+
return { email, result };
|
|
227
|
+
}));
|
|
228
|
+
for (const { email, result } of batchResults) {
|
|
229
|
+
results.set(email, result);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return results;
|
|
233
|
+
}
|
|
@@ -87,6 +87,7 @@ function extractDomain(candidate) {
|
|
|
87
87
|
function createConstructProvider(config) {
|
|
88
88
|
const maxAttempts = config?.maxAttempts ?? 8;
|
|
89
89
|
const timeoutMs = config?.timeoutMs ?? 5000;
|
|
90
|
+
const smtpVerifyDelayMs = config?.smtpVerifyDelayMs ?? 2000; // Delay between SMTP checks
|
|
90
91
|
async function fetchEmail(candidate) {
|
|
91
92
|
const { first, last } = extractNames(candidate);
|
|
92
93
|
const domain = extractDomain(candidate);
|
|
@@ -100,22 +101,79 @@ function createConstructProvider(config) {
|
|
|
100
101
|
}
|
|
101
102
|
const candidates = buildCandidates({ first, last, domain });
|
|
102
103
|
const max = Math.min(candidates.length, maxAttempts);
|
|
104
|
+
// First, check if domain is catch-all
|
|
105
|
+
const catchAllResult = await (0, mx_1.checkDomainCatchAll)(domain, { timeoutMs: 10000 });
|
|
106
|
+
const isCatchAll = catchAllResult.isCatchAll;
|
|
103
107
|
// Collect ALL valid email patterns (not just first match)
|
|
104
108
|
const validEmails = [];
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
109
|
+
// Track all attempted patterns for debugging
|
|
110
|
+
const attemptedPatterns = [];
|
|
111
|
+
// If NOT catch-all, we can verify each email via SMTP
|
|
112
|
+
if (isCatchAll === false) {
|
|
113
|
+
// Verify emails one by one, stop when we find a valid one
|
|
114
|
+
const emailsToVerify = candidates.slice(0, max);
|
|
115
|
+
for (let i = 0; i < emailsToVerify.length; i++) {
|
|
116
|
+
const email = emailsToVerify[i];
|
|
117
|
+
// If we already found a valid email, skip the rest
|
|
118
|
+
if (validEmails.length > 0) {
|
|
119
|
+
attemptedPatterns.push({ email, status: 'skipped' });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Add delay between checks (except first one)
|
|
123
|
+
if (i > 0) {
|
|
124
|
+
await new Promise((resolve) => setTimeout(resolve, smtpVerifyDelayMs));
|
|
125
|
+
}
|
|
126
|
+
// Verify single email
|
|
127
|
+
const results = await (0, mx_1.verifyEmailsExist)([email], { delayMs: 0, timeoutMs });
|
|
128
|
+
const result = results[0];
|
|
129
|
+
if (result.exists === true) {
|
|
130
|
+
// Email confirmed to exist!
|
|
131
|
+
attemptedPatterns.push({ email, status: 'exists' });
|
|
132
|
+
validEmails.push({
|
|
133
|
+
email: result.email,
|
|
134
|
+
verified: true,
|
|
135
|
+
confidence: 95, // High confidence - SMTP verified
|
|
136
|
+
isCatchAll: false,
|
|
137
|
+
metadata: {
|
|
138
|
+
pattern: result.email.split('@')[0],
|
|
139
|
+
mxRecords: catchAllResult.mxRecords,
|
|
140
|
+
smtpVerified: true,
|
|
141
|
+
attemptedPatterns, // Include what was tried
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
// Found one! Stop checking more
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
else if (result.exists === false) {
|
|
148
|
+
attemptedPatterns.push({ email, status: 'not_found' });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
attemptedPatterns.push({ email, status: 'unknown' });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// If no valid email found, include attempted patterns in metadata
|
|
155
|
+
if (validEmails.length === 0 && attemptedPatterns.length > 0) {
|
|
156
|
+
// Return null but could add metadata about attempts
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Catch-all or unknown - fall back to MX verification only
|
|
161
|
+
for (let i = 0; i < max; i++) {
|
|
162
|
+
const email = candidates[i];
|
|
163
|
+
const verification = await (0, mx_1.verifyEmailMx)(email, { timeoutMs });
|
|
164
|
+
if (verification.valid === true && verification.confidence >= 50) {
|
|
165
|
+
validEmails.push({
|
|
166
|
+
email,
|
|
167
|
+
verified: true,
|
|
168
|
+
confidence: verification.confidence,
|
|
169
|
+
isCatchAll: isCatchAll ?? undefined,
|
|
170
|
+
metadata: {
|
|
171
|
+
pattern: email.split('@')[0],
|
|
172
|
+
mxRecords: verification.mxRecords,
|
|
173
|
+
smtpVerified: false,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
119
177
|
}
|
|
120
178
|
}
|
|
121
179
|
if (validEmails.length === 0) {
|
|
@@ -7,62 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.createHunterProvider = createHunterProvider;
|
|
10
|
+
const http_retry_1 = require("../utils/http-retry");
|
|
10
11
|
const API_BASE = "https://api.hunter.io/v2";
|
|
11
|
-
/**
|
|
12
|
-
* Delay helper for retry logic
|
|
13
|
-
*/
|
|
14
|
-
async function delay(ms) {
|
|
15
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Check if value is truthy
|
|
19
|
-
*/
|
|
20
|
-
function truthy(v) {
|
|
21
|
-
return v !== undefined && v !== null && String(v).length > 0;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Map Hunter verification status to boolean
|
|
25
|
-
*/
|
|
26
|
-
function mapVerified(status) {
|
|
27
|
-
if (!status)
|
|
28
|
-
return undefined;
|
|
29
|
-
const s = String(status).toLowerCase();
|
|
30
|
-
if (s === "valid" || s === "deliverable")
|
|
31
|
-
return true;
|
|
32
|
-
if (s === "invalid" || s === "undeliverable")
|
|
33
|
-
return false;
|
|
34
|
-
return undefined; // catch-all/unknown/webmail -> leave undefined
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* HTTP request with retry on 429 rate limit
|
|
38
|
-
*/
|
|
39
|
-
async function requestWithRetry(url, retries = 1, backoffMs = 200) {
|
|
40
|
-
let lastErr;
|
|
41
|
-
for (let i = 0; i <= retries; i++) {
|
|
42
|
-
try {
|
|
43
|
-
const res = await fetch(url);
|
|
44
|
-
// Retry on rate limit
|
|
45
|
-
if (res?.status === 429 && i < retries) {
|
|
46
|
-
await delay(backoffMs * Math.pow(2, i));
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (!res || res.status >= 400) {
|
|
50
|
-
lastErr = new Error(`hunter_http_${res?.status ?? "error"}`);
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
const json = (await res.json());
|
|
54
|
-
return json;
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
lastErr = err;
|
|
58
|
-
if (i < retries) {
|
|
59
|
-
await delay(backoffMs * Math.pow(2, i));
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
throw lastErr ?? new Error("hunter_http_error");
|
|
65
|
-
}
|
|
66
12
|
/**
|
|
67
13
|
* Extract name and domain from candidate
|
|
68
14
|
*/
|
|
@@ -99,7 +45,7 @@ function createHunterProvider(config) {
|
|
|
99
45
|
let url = null;
|
|
100
46
|
let isEmailFinder = false;
|
|
101
47
|
// Use email-finder if we have name components
|
|
102
|
-
if (truthy(first) && truthy(last) && truthy(domain)) {
|
|
48
|
+
if ((0, http_retry_1.truthy)(first) && (0, http_retry_1.truthy)(last) && (0, http_retry_1.truthy)(domain)) {
|
|
103
49
|
const qs = new URLSearchParams({
|
|
104
50
|
api_key: String(apiKey),
|
|
105
51
|
domain: String(domain),
|
|
@@ -110,7 +56,7 @@ function createHunterProvider(config) {
|
|
|
110
56
|
isEmailFinder = true;
|
|
111
57
|
}
|
|
112
58
|
// Fall back to domain-search if only domain available (can return multiple)
|
|
113
|
-
else if (truthy(domain)) {
|
|
59
|
+
else if ((0, http_retry_1.truthy)(domain)) {
|
|
114
60
|
const qs = new URLSearchParams({
|
|
115
61
|
api_key: String(apiKey),
|
|
116
62
|
domain: String(domain),
|
|
@@ -122,7 +68,7 @@ function createHunterProvider(config) {
|
|
|
122
68
|
return null; // Can't search without domain
|
|
123
69
|
}
|
|
124
70
|
try {
|
|
125
|
-
const json = await
|
|
71
|
+
const json = await (0, http_retry_1.getWithRetry)(url, undefined, { retries: 1, backoffMs: 100 });
|
|
126
72
|
// Parse email-finder response shape (single result)
|
|
127
73
|
if (isEmailFinder) {
|
|
128
74
|
const ef = (json && (json.data || json.result));
|
|
@@ -131,7 +77,7 @@ function createHunterProvider(config) {
|
|
|
131
77
|
const score = typeof ef.score === "number"
|
|
132
78
|
? ef.score
|
|
133
79
|
: Number(ef.score ?? 0) || undefined;
|
|
134
|
-
const verified =
|
|
80
|
+
const verified = (0, http_retry_1.mapVerifiedStatus)(ef?.verification?.status ?? ef?.status);
|
|
135
81
|
if (!email)
|
|
136
82
|
return null;
|
|
137
83
|
return { email, verified, score };
|
|
@@ -158,7 +104,7 @@ function createHunterProvider(config) {
|
|
|
158
104
|
const score = typeof hit?.confidence === "number"
|
|
159
105
|
? hit.confidence
|
|
160
106
|
: Number(hit?.confidence ?? 0) || 50;
|
|
161
|
-
const verified =
|
|
107
|
+
const verified = (0, http_retry_1.mapVerifiedStatus)(hit?.verification?.status ?? hit?.status);
|
|
162
108
|
emails.push({
|
|
163
109
|
email,
|
|
164
110
|
verified,
|
|
@@ -5,5 +5,6 @@ export { createConstructProvider } from './construct';
|
|
|
5
5
|
export { createLddProvider } from './ldd';
|
|
6
6
|
export { createSmartProspectProvider } from './smartprospect';
|
|
7
7
|
export { createHunterProvider } from './hunter';
|
|
8
|
-
export { createApolloProvider } from './apollo';
|
|
9
8
|
export { createDropcontactProvider } from './dropcontact';
|
|
9
|
+
export { createBouncerProvider, verifyEmailWithBouncer, checkCatchAllDomain, verifyEmailsBatch } from './bouncer';
|
|
10
|
+
export { createSnovioProvider, findEmailsWithSnovio, verifyEmailWithSnovio, clearSnovioTokenCache } from './snovio';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Email Enrichment Providers
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.clearSnovioTokenCache = exports.verifyEmailWithSnovio = exports.findEmailsWithSnovio = exports.createSnovioProvider = exports.verifyEmailsBatch = exports.checkCatchAllDomain = exports.verifyEmailWithBouncer = exports.createBouncerProvider = exports.createDropcontactProvider = exports.createHunterProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = void 0;
|
|
7
7
|
var construct_1 = require("./construct");
|
|
8
8
|
Object.defineProperty(exports, "createConstructProvider", { enumerable: true, get: function () { return construct_1.createConstructProvider; } });
|
|
9
9
|
var ldd_1 = require("./ldd");
|
|
@@ -12,7 +12,15 @@ var smartprospect_1 = require("./smartprospect");
|
|
|
12
12
|
Object.defineProperty(exports, "createSmartProspectProvider", { enumerable: true, get: function () { return smartprospect_1.createSmartProspectProvider; } });
|
|
13
13
|
var hunter_1 = require("./hunter");
|
|
14
14
|
Object.defineProperty(exports, "createHunterProvider", { enumerable: true, get: function () { return hunter_1.createHunterProvider; } });
|
|
15
|
-
var apollo_1 = require("./apollo");
|
|
16
|
-
Object.defineProperty(exports, "createApolloProvider", { enumerable: true, get: function () { return apollo_1.createApolloProvider; } });
|
|
17
15
|
var dropcontact_1 = require("./dropcontact");
|
|
18
16
|
Object.defineProperty(exports, "createDropcontactProvider", { enumerable: true, get: function () { return dropcontact_1.createDropcontactProvider; } });
|
|
17
|
+
var bouncer_1 = require("./bouncer");
|
|
18
|
+
Object.defineProperty(exports, "createBouncerProvider", { enumerable: true, get: function () { return bouncer_1.createBouncerProvider; } });
|
|
19
|
+
Object.defineProperty(exports, "verifyEmailWithBouncer", { enumerable: true, get: function () { return bouncer_1.verifyEmailWithBouncer; } });
|
|
20
|
+
Object.defineProperty(exports, "checkCatchAllDomain", { enumerable: true, get: function () { return bouncer_1.checkCatchAllDomain; } });
|
|
21
|
+
Object.defineProperty(exports, "verifyEmailsBatch", { enumerable: true, get: function () { return bouncer_1.verifyEmailsBatch; } });
|
|
22
|
+
var snovio_1 = require("./snovio");
|
|
23
|
+
Object.defineProperty(exports, "createSnovioProvider", { enumerable: true, get: function () { return snovio_1.createSnovioProvider; } });
|
|
24
|
+
Object.defineProperty(exports, "findEmailsWithSnovio", { enumerable: true, get: function () { return snovio_1.findEmailsWithSnovio; } });
|
|
25
|
+
Object.defineProperty(exports, "verifyEmailWithSnovio", { enumerable: true, get: function () { return snovio_1.verifyEmailWithSnovio; } });
|
|
26
|
+
Object.defineProperty(exports, "clearSnovioTokenCache", { enumerable: true, get: function () { return snovio_1.clearSnovioTokenCache; } });
|
|
@@ -13,43 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
exports.extractNumericLinkedInId = extractNumericLinkedInId;
|
|
14
14
|
exports.createLddProvider = createLddProvider;
|
|
15
15
|
const validation_1 = require("../utils/validation");
|
|
16
|
-
|
|
17
|
-
* Delay helper for retry logic
|
|
18
|
-
*/
|
|
19
|
-
async function delay(ms) {
|
|
20
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* HTTP request with retry on 429 rate limit
|
|
24
|
-
*/
|
|
25
|
-
async function requestWithRetry(url, token, retries = 1, backoffMs = 200) {
|
|
26
|
-
let lastErr;
|
|
27
|
-
for (let i = 0; i <= retries; i++) {
|
|
28
|
-
try {
|
|
29
|
-
const res = await fetch(url, {
|
|
30
|
-
method: "GET",
|
|
31
|
-
headers: {
|
|
32
|
-
Authorization: `Bearer ${token}`,
|
|
33
|
-
"Content-Type": "application/json",
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
// Retry on rate limit
|
|
37
|
-
if (res?.status === 429 && i < retries) {
|
|
38
|
-
await delay(backoffMs * Math.pow(2, i));
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
return res;
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
lastErr = err;
|
|
45
|
-
if (i < retries) {
|
|
46
|
-
await delay(backoffMs * Math.pow(2, i));
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
throw lastErr ?? new Error("ldd_http_error");
|
|
52
|
-
}
|
|
16
|
+
const http_retry_1 = require("../utils/http-retry");
|
|
53
17
|
/**
|
|
54
18
|
* Extract numeric LinkedIn ID from various formats:
|
|
55
19
|
* - Direct number: "307567"
|
|
@@ -162,11 +126,8 @@ function createLddProvider(config) {
|
|
|
162
126
|
async function lookupByNumericId(numericId) {
|
|
163
127
|
try {
|
|
164
128
|
const endpoint = `${apiUrl}/api/v1/profiles/by-numeric-id/${encodeURIComponent(numericId)}`;
|
|
165
|
-
const response = await
|
|
166
|
-
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
return parseResponse(await response.json());
|
|
129
|
+
const response = await (0, http_retry_1.getWithRetry)(endpoint, { Authorization: `Bearer ${apiToken}` }, { retries: 1, backoffMs: 100 });
|
|
130
|
+
return parseResponse(response);
|
|
170
131
|
}
|
|
171
132
|
catch {
|
|
172
133
|
return null;
|
|
@@ -175,11 +136,8 @@ function createLddProvider(config) {
|
|
|
175
136
|
async function lookupByUsername(username) {
|
|
176
137
|
try {
|
|
177
138
|
const endpoint = `${apiUrl}/api/v1/profiles/by-username/${encodeURIComponent(username)}`;
|
|
178
|
-
const response = await
|
|
179
|
-
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
return parseResponse(await response.json());
|
|
139
|
+
const response = await (0, http_retry_1.getWithRetry)(endpoint, { Authorization: `Bearer ${apiToken}` }, { retries: 1, backoffMs: 100 });
|
|
140
|
+
return parseResponse(response);
|
|
183
141
|
}
|
|
184
142
|
catch {
|
|
185
143
|
return null;
|
|
@@ -17,13 +17,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.createSmartProspectProvider = createSmartProspectProvider;
|
|
18
18
|
exports.createSmartProspectClient = createSmartProspectClient;
|
|
19
19
|
const smartlead_auth_1 = require("../auth/smartlead-auth");
|
|
20
|
+
const http_retry_1 = require("../utils/http-retry");
|
|
20
21
|
const DEFAULT_API_URL = "https://prospect-api.smartlead.ai/api/search-email-leads";
|
|
21
|
-
/**
|
|
22
|
-
* Delay helper for retry logic
|
|
23
|
-
*/
|
|
24
|
-
async function delay(ms) {
|
|
25
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
26
|
-
}
|
|
27
22
|
const DEFAULT_POLLING_CONFIG = {
|
|
28
23
|
initialDelay: 500,
|
|
29
24
|
pollInterval: 1000,
|
|
@@ -40,7 +35,7 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
|
|
|
40
35
|
const res = await fetch(url, options);
|
|
41
36
|
// Retry on rate limit
|
|
42
37
|
if (res.status === 429 && i < retries) {
|
|
43
|
-
await delay(backoffMs * Math.pow(2, i));
|
|
38
|
+
await (0, http_retry_1.delay)(backoffMs * Math.pow(2, i));
|
|
44
39
|
continue;
|
|
45
40
|
}
|
|
46
41
|
if (!res.ok) {
|
|
@@ -52,7 +47,7 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
|
|
|
52
47
|
catch (err) {
|
|
53
48
|
lastErr = err;
|
|
54
49
|
if (i < retries) {
|
|
55
|
-
await delay(backoffMs * Math.pow(2, i));
|
|
50
|
+
await (0, http_retry_1.delay)(backoffMs * Math.pow(2, i));
|
|
56
51
|
continue;
|
|
57
52
|
}
|
|
58
53
|
}
|
|
@@ -214,7 +209,7 @@ function createSmartProspectProvider(config) {
|
|
|
214
209
|
async function pollForContactResults(filterId, expectedCount) {
|
|
215
210
|
const { initialDelay, pollInterval, maxAttempts, maxWaitTime } = DEFAULT_POLLING_CONFIG;
|
|
216
211
|
const startTime = Date.now();
|
|
217
|
-
await delay(initialDelay);
|
|
212
|
+
await (0, http_retry_1.delay)(initialDelay);
|
|
218
213
|
const makeRequest = async (token) => {
|
|
219
214
|
return requestWithRetry(`${apiUrl}/get-contacts`, {
|
|
220
215
|
method: "POST",
|
|
@@ -243,13 +238,13 @@ function createSmartProspectProvider(config) {
|
|
|
243
238
|
return result;
|
|
244
239
|
}
|
|
245
240
|
}
|
|
246
|
-
await delay(pollInterval);
|
|
241
|
+
await (0, http_retry_1.delay)(pollInterval);
|
|
247
242
|
}
|
|
248
243
|
catch (err) {
|
|
249
244
|
if (err instanceof Error && err.message.includes("401") && hasCredentials) {
|
|
250
245
|
await handleAuthError();
|
|
251
246
|
}
|
|
252
|
-
await delay(pollInterval);
|
|
247
|
+
await (0, http_retry_1.delay)(pollInterval);
|
|
253
248
|
}
|
|
254
249
|
}
|
|
255
250
|
// Return last result
|
|
@@ -628,7 +623,7 @@ function createSmartProspectClient(config) {
|
|
|
628
623
|
const { initialDelay, pollInterval, maxAttempts, maxWaitTime } = pollingConfig;
|
|
629
624
|
const startTime = Date.now();
|
|
630
625
|
// Wait initial delay before first poll
|
|
631
|
-
await delay(initialDelay);
|
|
626
|
+
await (0, http_retry_1.delay)(initialDelay);
|
|
632
627
|
const makeRequest = async (token) => {
|
|
633
628
|
return requestWithRetry(`${apiUrl}/get-contacts`, {
|
|
634
629
|
method: "POST",
|
|
@@ -660,7 +655,7 @@ function createSmartProspectClient(config) {
|
|
|
660
655
|
}
|
|
661
656
|
}
|
|
662
657
|
// Wait before next poll
|
|
663
|
-
await delay(pollInterval);
|
|
658
|
+
await (0, http_retry_1.delay)(pollInterval);
|
|
664
659
|
}
|
|
665
660
|
catch (err) {
|
|
666
661
|
// On 401, clear token and retry
|
|
@@ -670,7 +665,7 @@ function createSmartProspectClient(config) {
|
|
|
670
665
|
await handleAuthError();
|
|
671
666
|
}
|
|
672
667
|
// Continue polling on errors
|
|
673
|
-
await delay(pollInterval);
|
|
668
|
+
await (0, http_retry_1.delay)(pollInterval);
|
|
674
669
|
}
|
|
675
670
|
}
|
|
676
671
|
// Return last result even if not complete
|