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.
@@ -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
- for (let i = 0; i < max; i++) {
106
- const email = candidates[i];
107
- const verification = await (0, mx_1.verifyEmailMx)(email, { timeoutMs });
108
- if (verification.valid === true && verification.confidence >= 50) {
109
- validEmails.push({
110
- email,
111
- verified: true,
112
- confidence: verification.confidence,
113
- isCatchAll: verification.isCatchAll,
114
- metadata: {
115
- pattern: email.split('@')[0], // The local part pattern used
116
- mxRecords: verification.mxRecords,
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 requestWithRetry(url, 1, 100);
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 = mapVerified(ef?.verification?.status ?? ef?.status);
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 = mapVerified(hit?.verification?.status ?? hit?.status);
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.createDropcontactProvider = exports.createApolloProvider = exports.createHunterProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = void 0;
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 requestWithRetry(endpoint, apiToken, 1, 100);
166
- if (!response.ok) {
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 requestWithRetry(endpoint, apiToken, 1, 100);
179
- if (!response.ok) {
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