linkedin-secret-sauce 0.12.3 → 0.12.5
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 +6 -49
- package/dist/enrichment/index.d.ts +3 -5
- package/dist/enrichment/index.js +4 -20
- package/dist/enrichment/matching.d.ts +4 -8
- package/dist/enrichment/matching.js +165 -84
- package/dist/enrichment/orchestrator.d.ts +1 -1
- package/dist/enrichment/orchestrator.js +51 -44
- package/dist/enrichment/providers/bounceban.js +19 -10
- package/dist/enrichment/providers/construct.js +97 -89
- package/dist/enrichment/providers/cosiall.js +2 -2
- package/dist/enrichment/providers/hunter.js +2 -1
- package/dist/enrichment/providers/index.d.ts +0 -2
- package/dist/enrichment/providers/index.js +1 -8
- package/dist/enrichment/providers/ldd.js +4 -2
- package/dist/enrichment/providers/smartprospect.d.ts +5 -2
- package/dist/enrichment/providers/smartprospect.js +64 -27
- package/dist/enrichment/providers/snovio.d.ts +4 -4
- package/dist/enrichment/providers/snovio.js +21 -17
- package/dist/enrichment/providers/trykitt.d.ts +2 -2
- package/dist/enrichment/providers/trykitt.js +86 -21
- package/dist/enrichment/types.d.ts +8 -71
- package/dist/enrichment/types.js +3 -8
- package/dist/enrichment/utils/rate-limiter.js +0 -2
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -2
- package/docs/ENRICHMENT.md +3 -45
- package/docs/INTEGRATION.md +1 -3
- package/docs/PLAYGROUND.md +4 -9
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/LinkedInClientError.html +4 -4
- package/docs/api/functions/_testGetAccountCookies.html +2 -2
- package/docs/api/functions/_testGetAccountEntry.html +2 -2
- package/docs/api/functions/_testGetAllAccountIds.html +2 -2
- package/docs/api/functions/_testGetPoolState.html +2 -2
- package/docs/api/functions/adminResetAccount.html +1 -1
- package/docs/api/functions/adminSetCooldown.html +1 -1
- package/docs/api/functions/buildCookieHeader.html +1 -1
- package/docs/api/functions/clearAllSmartLeadTokens.html +2 -2
- package/docs/api/functions/clearRequestHistory.html +1 -1
- package/docs/api/functions/clearSessionAccount.html +1 -1
- package/docs/api/functions/clearSmartLeadToken.html +2 -2
- package/docs/api/functions/createEnrichmentClient.html +3 -3
- package/docs/api/functions/extractCsrfToken.html +1 -1
- package/docs/api/functions/extractLinkedInHandle.html +2 -2
- package/docs/api/functions/fetchCookiesFromCosiall.html +2 -2
- package/docs/api/functions/fetchProfileEmailsFromCosiall.html +2 -2
- package/docs/api/functions/forceRefreshCookies.html +1 -1
- package/docs/api/functions/getAccountForSession.html +1 -1
- package/docs/api/functions/getAccountsSummary.html +1 -1
- package/docs/api/functions/getCompaniesBatch.html +2 -2
- package/docs/api/functions/getCompanyById.html +2 -2
- package/docs/api/functions/getCompanyByUrl.html +1 -1
- package/docs/api/functions/getConfig.html +1 -1
- package/docs/api/functions/getCookiePoolHealth.html +1 -1
- package/docs/api/functions/getProfileByUrn.html +2 -2
- package/docs/api/functions/getProfileByVanity.html +2 -2
- package/docs/api/functions/getProfilesBatch.html +1 -1
- package/docs/api/functions/getRequestHistory.html +1 -1
- package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -1
- package/docs/api/functions/getSalesNavigatorProfileFull.html +2 -2
- package/docs/api/functions/getSmartLeadToken.html +1 -1
- package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -2
- package/docs/api/functions/getSmartLeadUser.html +2 -2
- package/docs/api/functions/getSnapshot.html +1 -1
- package/docs/api/functions/getYearsAtCompanyOptions.html +2 -2
- package/docs/api/functions/getYearsInPositionOptions.html +2 -2
- package/docs/api/functions/getYearsOfExperienceOptions.html +2 -2
- package/docs/api/functions/incrementMetric.html +1 -1
- package/docs/api/functions/initializeCookiePool.html +1 -1
- package/docs/api/functions/initializeLinkedInClient.html +1 -1
- package/docs/api/functions/isBusinessEmail.html +2 -2
- package/docs/api/functions/isDisposableDomain.html +2 -2
- package/docs/api/functions/isDisposableEmail.html +2 -2
- package/docs/api/functions/isPersonalDomain.html +2 -2
- package/docs/api/functions/isPersonalEmail.html +2 -2
- package/docs/api/functions/isRoleAccount.html +2 -2
- package/docs/api/functions/isValidEmailSyntax.html +2 -2
- package/docs/api/functions/parseFullProfile.html +2 -2
- package/docs/api/functions/parseSalesSearchResults.html +1 -1
- package/docs/api/functions/reportAccountFailure.html +1 -1
- package/docs/api/functions/reportAccountSuccess.html +1 -1
- package/docs/api/functions/resolveCompanyUniversalName.html +1 -1
- package/docs/api/functions/searchSalesLeads.html +2 -2
- package/docs/api/functions/selectAccountForRequest.html +1 -1
- package/docs/api/functions/setAccountForSession.html +1 -1
- package/docs/api/functions/typeahead.html +1 -1
- package/docs/api/functions/verifyEmailMx.html +1 -1
- package/docs/api/hierarchy.html +1 -1
- package/docs/api/index.html +3 -3
- package/docs/api/interfaces/AccountCookies.html +2 -2
- package/docs/api/interfaces/BatchEnrichmentOptions.html +8 -8
- package/docs/api/interfaces/CacheAdapter.html +4 -4
- package/docs/api/interfaces/CanonicalEmail.html +8 -8
- package/docs/api/interfaces/Company.html +2 -2
- package/docs/api/interfaces/ConstructConfig.html +5 -5
- package/docs/api/interfaces/CosiallProfileEmailsResponse.html +6 -6
- package/docs/api/interfaces/EnrichmentCandidate.html +4 -4
- package/docs/api/interfaces/EnrichmentClient.html +6 -6
- package/docs/api/interfaces/EnrichmentClientConfig.html +7 -7
- package/docs/api/interfaces/EnrichmentLogger.html +3 -3
- package/docs/api/interfaces/EnrichmentOptions.html +6 -6
- package/docs/api/interfaces/HunterConfig.html +3 -3
- package/docs/api/interfaces/LddConfig.html +3 -3
- package/docs/api/interfaces/LddProfileData.html +2 -2
- package/docs/api/interfaces/LinkedInClientConfig.html +2 -2
- package/docs/api/interfaces/LinkedInCookie.html +2 -2
- package/docs/api/interfaces/LinkedInPosition.html +2 -2
- package/docs/api/interfaces/LinkedInProfile.html +2 -2
- package/docs/api/interfaces/LinkedInSpotlightBadge.html +2 -2
- package/docs/api/interfaces/LinkedInTenure.html +2 -2
- package/docs/api/interfaces/Metrics.html +2 -2
- package/docs/api/interfaces/MetricsSnapshot.html +2 -2
- package/docs/api/interfaces/ProfileEducation.html +2 -2
- package/docs/api/interfaces/ProfileEmailsLookupOptions.html +5 -5
- package/docs/api/interfaces/ProfilePosition.html +2 -2
- package/docs/api/interfaces/ProfileSkill.html +2 -2
- package/docs/api/interfaces/ProviderResult.html +6 -6
- package/docs/api/interfaces/ProvidersConfig.html +6 -9
- package/docs/api/interfaces/RequestHistoryEntry.html +2 -2
- package/docs/api/interfaces/SalesLeadSearchResult.html +2 -2
- package/docs/api/interfaces/SalesNavigatorContactInfo.html +2 -2
- package/docs/api/interfaces/SalesNavigatorPosition.html +2 -2
- package/docs/api/interfaces/SalesNavigatorProfile.html +2 -2
- package/docs/api/interfaces/SalesNavigatorProfileFull.html +4 -4
- package/docs/api/interfaces/SearchSalesResult.html +2 -2
- package/docs/api/interfaces/SmartLeadAuthConfig.html +4 -4
- package/docs/api/interfaces/SmartLeadCredentials.html +2 -2
- package/docs/api/interfaces/SmartLeadLoginResponse.html +2 -2
- package/docs/api/interfaces/SmartLeadUser.html +2 -2
- package/docs/api/interfaces/SmartProspectConfig.html +8 -8
- package/docs/api/interfaces/SmartProspectContact.html +2 -2
- package/docs/api/interfaces/SmartProspectSearchFilters.html +21 -21
- package/docs/api/interfaces/TypeaheadItem.html +2 -2
- package/docs/api/interfaces/TypeaheadResult.html +2 -2
- package/docs/api/interfaces/VerificationResult.html +9 -9
- package/docs/api/types/CostCallback.html +2 -2
- package/docs/api/types/Geo.html +2 -2
- package/docs/api/types/LddApiResponse.html +1 -1
- package/docs/api/types/ProviderFunc.html +2 -2
- package/docs/api/types/ProviderName.html +2 -2
- package/docs/api/types/SalesSearchFilters.html +2 -2
- package/docs/api/types/TypeaheadType.html +1 -1
- package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -1
- package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +3 -4
- package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -2
- package/docs/api/variables/FUNCTION_OPTIONS.html +1 -1
- package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -1
- package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -1
- package/docs/api/variables/PERSONAL_DOMAINS.html +2 -2
- package/docs/api/variables/PROVIDER_COSTS.html +3 -5
- package/docs/api/variables/REGION_OPTIONS.html +1 -1
- package/docs/api/variables/SENIORITY_OPTIONS.html +2 -2
- package/docs/api/variables/YEARS_OPTIONS.html +1 -1
- package/package.json +1 -1
- package/dist/enrichment/providers/apollo.d.ts +0 -11
- package/dist/enrichment/providers/apollo.js +0 -181
- package/dist/enrichment/providers/bouncer.d.ts +0 -67
- package/dist/enrichment/providers/bouncer.js +0 -231
- package/dist/enrichment/providers/dropcontact.d.ts +0 -22
- package/dist/enrichment/providers/dropcontact.js +0 -206
- package/docs/api/interfaces/DropcontactConfig.html +0 -3
|
@@ -1,231 +0,0 @@
|
|
|
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 noop_provider_1 = require("../utils/noop-provider");
|
|
23
|
-
const DEFAULT_API_URL = "https://api.usebouncer.com/v1.1";
|
|
24
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
25
|
-
/**
|
|
26
|
-
* Map Bouncer status to confidence score
|
|
27
|
-
*/
|
|
28
|
-
function statusToConfidence(status, response) {
|
|
29
|
-
switch (status) {
|
|
30
|
-
case "deliverable":
|
|
31
|
-
// High confidence - email verified as deliverable
|
|
32
|
-
// Reduce slightly if catch-all or high toxicity
|
|
33
|
-
let score = 95;
|
|
34
|
-
if (response.acceptAll)
|
|
35
|
-
score -= 20; // Catch-all reduces confidence
|
|
36
|
-
if (response.toxicity && response.toxicity >= 3)
|
|
37
|
-
score -= 10;
|
|
38
|
-
if (response.role)
|
|
39
|
-
score -= 5; // Role accounts slightly lower
|
|
40
|
-
return Math.max(50, score);
|
|
41
|
-
case "risky":
|
|
42
|
-
// Medium confidence - risky but might work
|
|
43
|
-
return response.acceptAll ? 40 : 50;
|
|
44
|
-
case "undeliverable":
|
|
45
|
-
// Email does not exist
|
|
46
|
-
return 0;
|
|
47
|
-
case "unknown":
|
|
48
|
-
// Could not verify
|
|
49
|
-
return 30;
|
|
50
|
-
default:
|
|
51
|
-
return 0;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Extract email candidates from the enrichment candidate
|
|
56
|
-
* Bouncer is a VERIFICATION provider, so it needs emails to verify
|
|
57
|
-
*/
|
|
58
|
-
function extractEmailsToVerify(candidate) {
|
|
59
|
-
const emails = [];
|
|
60
|
-
// Check if candidate has pre-generated email patterns in metadata
|
|
61
|
-
const metadata = candidate._emailCandidates;
|
|
62
|
-
if (Array.isArray(metadata)) {
|
|
63
|
-
emails.push(...metadata.filter((e) => typeof e === "string"));
|
|
64
|
-
}
|
|
65
|
-
return emails;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Verify a single email via Bouncer API
|
|
69
|
-
*/
|
|
70
|
-
async function verifyEmail(email, apiKey, apiUrl, timeoutMs) {
|
|
71
|
-
const controller = new AbortController();
|
|
72
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
73
|
-
try {
|
|
74
|
-
const url = `${apiUrl}/email/verify?email=${encodeURIComponent(email)}`;
|
|
75
|
-
const response = await fetch(url, {
|
|
76
|
-
method: "GET",
|
|
77
|
-
headers: {
|
|
78
|
-
"x-api-key": apiKey,
|
|
79
|
-
Accept: "application/json",
|
|
80
|
-
},
|
|
81
|
-
signal: controller.signal,
|
|
82
|
-
});
|
|
83
|
-
if (!response.ok) {
|
|
84
|
-
// All HTTP errors return null - let caller decide how to handle
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const data = (await response.json());
|
|
88
|
-
return data;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// All errors (network, timeout, etc.) return null
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
clearTimeout(timeoutId);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Create the Bouncer verification provider
|
|
100
|
-
*
|
|
101
|
-
* NOTE: This provider is a VERIFIER, not a FINDER. It verifies emails
|
|
102
|
-
* that were generated by the construct provider or passed in the candidate.
|
|
103
|
-
*
|
|
104
|
-
* Usage in the enrichment flow:
|
|
105
|
-
* 1. construct provider generates email patterns
|
|
106
|
-
* 2. bouncer provider verifies which patterns are deliverable
|
|
107
|
-
* 3. Only verified emails are returned
|
|
108
|
-
*/
|
|
109
|
-
function createBouncerProvider(config) {
|
|
110
|
-
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
111
|
-
if (!apiKey) {
|
|
112
|
-
return (0, noop_provider_1.createNoOpProvider)("bouncer");
|
|
113
|
-
}
|
|
114
|
-
async function verifyEmails(candidate) {
|
|
115
|
-
const emailsToVerify = extractEmailsToVerify(candidate);
|
|
116
|
-
// If no emails to verify, return null
|
|
117
|
-
// The orchestrator will need to pass emails from construct
|
|
118
|
-
if (emailsToVerify.length === 0) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
const verifiedEmails = [];
|
|
122
|
-
// Verify each email candidate
|
|
123
|
-
for (const email of emailsToVerify) {
|
|
124
|
-
try {
|
|
125
|
-
const result = await verifyEmail(email, apiKey, apiUrl, timeoutMs);
|
|
126
|
-
if (result) {
|
|
127
|
-
const confidence = statusToConfidence(result.status, result);
|
|
128
|
-
// Only include emails that are potentially deliverable
|
|
129
|
-
if (result.status === "deliverable" || result.status === "risky") {
|
|
130
|
-
verifiedEmails.push({
|
|
131
|
-
email: result.email,
|
|
132
|
-
verified: result.status === "deliverable",
|
|
133
|
-
confidence,
|
|
134
|
-
isCatchAll: result.acceptAll,
|
|
135
|
-
metadata: {
|
|
136
|
-
bouncerStatus: result.status,
|
|
137
|
-
bouncerReason: result.reason,
|
|
138
|
-
toxicity: result.toxicity,
|
|
139
|
-
isDisposable: result.disposable,
|
|
140
|
-
isRole: result.role,
|
|
141
|
-
isFreeProvider: result.free,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
// Continue with other emails on error
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (verifiedEmails.length === 0) {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
// Sort by confidence
|
|
156
|
-
verifiedEmails.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0));
|
|
157
|
-
return { emails: verifiedEmails };
|
|
158
|
-
}
|
|
159
|
-
// Mark provider name for orchestrator
|
|
160
|
-
verifyEmails.__name = "bouncer";
|
|
161
|
-
return verifyEmails;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Standalone function to verify a single email via Bouncer
|
|
165
|
-
*
|
|
166
|
-
* Useful for ad-hoc verification outside the enrichment flow.
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```typescript
|
|
170
|
-
* const result = await verifyEmailWithBouncer('test@example.com', {
|
|
171
|
-
* apiKey: process.env.BOUNCER_API_KEY,
|
|
172
|
-
* });
|
|
173
|
-
* console.log(result.status); // 'deliverable' | 'undeliverable' | 'risky' | 'unknown'
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
|
-
async function verifyEmailWithBouncer(email, config) {
|
|
177
|
-
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
178
|
-
return verifyEmail(email, apiKey, apiUrl, timeoutMs);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Check if a domain is catch-all via Bouncer
|
|
182
|
-
*
|
|
183
|
-
* Sends a verification request for a random email and checks acceptAll flag.
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```typescript
|
|
187
|
-
* const isCatchAll = await checkCatchAllDomain('example.com', {
|
|
188
|
-
* apiKey: process.env.BOUNCER_API_KEY,
|
|
189
|
-
* });
|
|
190
|
-
* ```
|
|
191
|
-
*/
|
|
192
|
-
async function checkCatchAllDomain(domain, config) {
|
|
193
|
-
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
194
|
-
// Generate a random email that almost certainly doesn't exist
|
|
195
|
-
const randomLocal = `bounce-test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
196
|
-
const testEmail = `${randomLocal}@${domain}`;
|
|
197
|
-
const result = await verifyEmail(testEmail, apiKey, apiUrl, timeoutMs);
|
|
198
|
-
if (!result) {
|
|
199
|
-
return null; // Could not determine
|
|
200
|
-
}
|
|
201
|
-
// If acceptAll is true, the domain accepts all emails (catch-all)
|
|
202
|
-
return result.acceptAll;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Verify multiple emails in batch via Bouncer
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* ```typescript
|
|
209
|
-
* const results = await verifyEmailsBatch(
|
|
210
|
-
* ['john@example.com', 'jane@example.com'],
|
|
211
|
-
* { apiKey: process.env.BOUNCER_API_KEY }
|
|
212
|
-
* );
|
|
213
|
-
* ```
|
|
214
|
-
*/
|
|
215
|
-
async function verifyEmailsBatch(emails, config) {
|
|
216
|
-
const { apiKey, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
217
|
-
const results = new Map();
|
|
218
|
-
// Verify in parallel with concurrency limit
|
|
219
|
-
const CONCURRENCY = 5;
|
|
220
|
-
for (let i = 0; i < emails.length; i += CONCURRENCY) {
|
|
221
|
-
const batch = emails.slice(i, i + CONCURRENCY);
|
|
222
|
-
const batchResults = await Promise.all(batch.map(async (email) => {
|
|
223
|
-
const result = await verifyEmail(email, apiKey, apiUrl, timeoutMs);
|
|
224
|
-
return { email, result };
|
|
225
|
-
}));
|
|
226
|
-
for (const { email, result } of batchResults) {
|
|
227
|
-
results.set(email, result);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return results;
|
|
231
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dropcontact Provider
|
|
3
|
-
*
|
|
4
|
-
* Dropcontact API for email finding and enrichment.
|
|
5
|
-
* Uses the batch enrichment API with polling for results.
|
|
6
|
-
*
|
|
7
|
-
* API Flow:
|
|
8
|
-
* 1. POST /batch to submit contacts for enrichment
|
|
9
|
-
* 2. GET /batch/{request_id} to poll for results
|
|
10
|
-
*
|
|
11
|
-
* Features:
|
|
12
|
-
* - Email finding from name + company/website
|
|
13
|
-
* - Email verification (deliverable/undeliverable)
|
|
14
|
-
* - GDPR compliant (EU-based)
|
|
15
|
-
*
|
|
16
|
-
* @see https://developer.dropcontact.com/
|
|
17
|
-
*/
|
|
18
|
-
import type { EnrichmentCandidate, ProviderResult, DropcontactConfig } from "../types";
|
|
19
|
-
/**
|
|
20
|
-
* Create the Dropcontact provider function
|
|
21
|
-
*/
|
|
22
|
-
export declare function createDropcontactProvider(config: DropcontactConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | null>;
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Dropcontact Provider
|
|
4
|
-
*
|
|
5
|
-
* Dropcontact API for email finding and enrichment.
|
|
6
|
-
* Uses the batch enrichment API with polling for results.
|
|
7
|
-
*
|
|
8
|
-
* API Flow:
|
|
9
|
-
* 1. POST /batch to submit contacts for enrichment
|
|
10
|
-
* 2. GET /batch/{request_id} to poll for results
|
|
11
|
-
*
|
|
12
|
-
* Features:
|
|
13
|
-
* - Email finding from name + company/website
|
|
14
|
-
* - Email verification (deliverable/undeliverable)
|
|
15
|
-
* - GDPR compliant (EU-based)
|
|
16
|
-
*
|
|
17
|
-
* @see https://developer.dropcontact.com/
|
|
18
|
-
*/
|
|
19
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.createDropcontactProvider = createDropcontactProvider;
|
|
21
|
-
const http_retry_1 = require("../utils/http-retry");
|
|
22
|
-
const noop_provider_1 = require("../utils/noop-provider");
|
|
23
|
-
const API_BASE = "https://api.dropcontact.io";
|
|
24
|
-
/**
|
|
25
|
-
* Extract inputs from candidate for Dropcontact API
|
|
26
|
-
*/
|
|
27
|
-
function extractInputs(candidate) {
|
|
28
|
-
const name = candidate.name || candidate.fullName || candidate.full_name;
|
|
29
|
-
const first = candidate.first ||
|
|
30
|
-
candidate.first_name ||
|
|
31
|
-
candidate.firstName ||
|
|
32
|
-
name?.split?.(" ")?.[0];
|
|
33
|
-
const last = candidate.last ||
|
|
34
|
-
candidate.last_name ||
|
|
35
|
-
candidate.lastName ||
|
|
36
|
-
name?.split?.(" ")?.slice(1).join(" ") ||
|
|
37
|
-
undefined;
|
|
38
|
-
const domain = candidate.domain ||
|
|
39
|
-
candidate.companyDomain ||
|
|
40
|
-
candidate.company_domain ||
|
|
41
|
-
undefined;
|
|
42
|
-
const company = candidate.company ||
|
|
43
|
-
candidate.currentCompany ||
|
|
44
|
-
candidate.organization ||
|
|
45
|
-
candidate.org ||
|
|
46
|
-
undefined;
|
|
47
|
-
const linkedin = candidate.linkedinUrl ||
|
|
48
|
-
candidate.linkedin_url ||
|
|
49
|
-
(candidate.linkedinHandle
|
|
50
|
-
? `https://www.linkedin.com/in/${candidate.linkedinHandle}`
|
|
51
|
-
: undefined) ||
|
|
52
|
-
(candidate.linkedin_handle
|
|
53
|
-
? `https://www.linkedin.com/in/${candidate.linkedin_handle}`
|
|
54
|
-
: undefined) ||
|
|
55
|
-
undefined;
|
|
56
|
-
return {
|
|
57
|
-
first_name: first,
|
|
58
|
-
last_name: last,
|
|
59
|
-
full_name: name,
|
|
60
|
-
company: company,
|
|
61
|
-
website: domain,
|
|
62
|
-
linkedin: linkedin,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Create the Dropcontact provider function
|
|
67
|
-
*/
|
|
68
|
-
function createDropcontactProvider(config) {
|
|
69
|
-
const { apiKey } = config;
|
|
70
|
-
if (!apiKey) {
|
|
71
|
-
return (0, noop_provider_1.createNoOpProvider)("dropcontact");
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Submit a batch request to Dropcontact
|
|
75
|
-
*/
|
|
76
|
-
async function submitBatch(contacts) {
|
|
77
|
-
const body = {
|
|
78
|
-
data: contacts,
|
|
79
|
-
siren: false, // We don't need French company registration numbers
|
|
80
|
-
language: "en",
|
|
81
|
-
};
|
|
82
|
-
try {
|
|
83
|
-
const response = await (0, http_retry_1.postWithRetry)(`${API_BASE}/batch`, body, { "X-Access-Token": apiKey }, { retries: 2, backoffMs: 500, timeoutMs: 30000 });
|
|
84
|
-
if (response.error) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
return response.request_id || null;
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Poll for batch results with exponential backoff
|
|
95
|
-
*/
|
|
96
|
-
async function pollResults(requestId, maxAttempts = 10, initialDelayMs = 2000) {
|
|
97
|
-
let delayMs = initialDelayMs;
|
|
98
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
99
|
-
await (0, http_retry_1.delay)(delayMs);
|
|
100
|
-
try {
|
|
101
|
-
const response = await (0, http_retry_1.getWithRetry)(`${API_BASE}/batch/${requestId}`, { "X-Access-Token": apiKey }, { retries: 1, backoffMs: 500, timeoutMs: 30000 });
|
|
102
|
-
// Check if processing is complete
|
|
103
|
-
if (response.error) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
// If we have data and status is finished (or we got data back), return it
|
|
107
|
-
if (response.data && response.data.length > 0) {
|
|
108
|
-
// Check if still processing
|
|
109
|
-
if (response.status === "pending") {
|
|
110
|
-
// Increase delay for next poll (max 10 seconds)
|
|
111
|
-
delayMs = Math.min(delayMs * 1.5, 10000);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
return response.data;
|
|
115
|
-
}
|
|
116
|
-
// If explicitly finished but no data
|
|
117
|
-
if (response.status === "finished") {
|
|
118
|
-
return response.data || null;
|
|
119
|
-
}
|
|
120
|
-
// Still processing, increase delay
|
|
121
|
-
delayMs = Math.min(delayMs * 1.5, 10000);
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// Network error, retry with backoff
|
|
125
|
-
delayMs = Math.min(delayMs * 2, 10000);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return null; // Timeout after max attempts
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Fetch email for a candidate
|
|
132
|
-
*/
|
|
133
|
-
async function fetchEmail(candidate) {
|
|
134
|
-
const contact = extractInputs(candidate);
|
|
135
|
-
// Need at least a name and company/website to search
|
|
136
|
-
const hasName = (0, http_retry_1.truthy)(contact.first_name) || (0, http_retry_1.truthy)(contact.full_name);
|
|
137
|
-
const hasCompanyInfo = (0, http_retry_1.truthy)(contact.company) || (0, http_retry_1.truthy)(contact.website);
|
|
138
|
-
const hasLinkedIn = (0, http_retry_1.truthy)(contact.linkedin);
|
|
139
|
-
if (!hasName && !hasLinkedIn) {
|
|
140
|
-
return null; // Need at least name or LinkedIn URL
|
|
141
|
-
}
|
|
142
|
-
if (!hasCompanyInfo && !hasLinkedIn) {
|
|
143
|
-
return null; // Need company info or LinkedIn URL
|
|
144
|
-
}
|
|
145
|
-
// Submit single contact as batch
|
|
146
|
-
const requestId = await submitBatch([contact]);
|
|
147
|
-
if (!requestId) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
// Poll for results
|
|
151
|
-
const results = await pollResults(requestId);
|
|
152
|
-
if (!results || results.length === 0) {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
// Extract email from first result
|
|
156
|
-
const enriched = results[0];
|
|
157
|
-
if (!enriched.email || enriched.email.length === 0) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
// Find the best email (prefer valid ones)
|
|
161
|
-
let bestEmail = null;
|
|
162
|
-
for (const emailObj of enriched.email) {
|
|
163
|
-
if (!emailObj.email)
|
|
164
|
-
continue;
|
|
165
|
-
// Prefer valid emails
|
|
166
|
-
if (emailObj.qualification === "valid") {
|
|
167
|
-
bestEmail = emailObj;
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
// Take catch-all if no valid found yet
|
|
171
|
-
if (!bestEmail ||
|
|
172
|
-
(bestEmail.qualification !== "valid" &&
|
|
173
|
-
emailObj.qualification === "catch_all")) {
|
|
174
|
-
bestEmail = emailObj;
|
|
175
|
-
}
|
|
176
|
-
// Take any email if nothing else
|
|
177
|
-
if (!bestEmail) {
|
|
178
|
-
bestEmail = emailObj;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (!bestEmail || !bestEmail.email) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
// Map qualification to verified status
|
|
185
|
-
const verified = (0, http_retry_1.mapVerifiedStatus)(bestEmail.qualification);
|
|
186
|
-
// Confidence based on qualification
|
|
187
|
-
let score = 50;
|
|
188
|
-
if (bestEmail.qualification === "valid") {
|
|
189
|
-
score = 95;
|
|
190
|
-
}
|
|
191
|
-
else if (bestEmail.qualification === "catch_all") {
|
|
192
|
-
score = 60;
|
|
193
|
-
}
|
|
194
|
-
else if (bestEmail.qualification === "invalid") {
|
|
195
|
-
score = 10;
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
email: bestEmail.email,
|
|
199
|
-
verified,
|
|
200
|
-
score,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
// Mark provider name for orchestrator
|
|
204
|
-
fetchEmail.__name = "dropcontact";
|
|
205
|
-
return fetchEmail;
|
|
206
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>DropcontactConfig | LinkedIn Secret Sauce - v0.12.3</title><meta name="description" content="Documentation for LinkedIn Secret Sauce"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">LinkedIn Secret Sauce - v0.12.3</a><div id="tsd-toolbar-links"><a href="https://github.com/enerage/LinkedInSecretSauce">GitHub</a><a href="https://www.npmjs.com/package/linkedin-secret-sauce">npm</a></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">DropcontactConfig</a></li></ul><h1>Interface DropcontactConfig</h1></div><section class="tsd-panel tsd-comment"><div class="tsd-comment tsd-typography"><p>Dropcontact provider configuration</p>
|
|
2
|
-
</div></section><div class="tsd-signature"><span class="tsd-signature-keyword">interface</span> <span class="tsd-kind-interface">DropcontactConfig</span> <span class="tsd-signature-symbol">{</span><br/> <a class="tsd-kind-property" href="#apikey">apiKey</a><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">;</span><br/><span class="tsd-signature-symbol">}</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/enerage/LinkedInSecretSauce/blob/142a9d0b85e83a724e9d23b89f1efc205b5a6dbd/src/enrichment/types.ts#L216">src/enrichment/types.ts:216</a></li></ul></aside><section class="tsd-panel-group tsd-index-group"><section class="tsd-panel tsd-index-panel"><details class="tsd-index-content tsd-accordion" open><summary class="tsd-accordion-summary tsd-index-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h5 class="tsd-index-heading uppercase">Index</h5></summary><div class="tsd-accordion-details"><section class="tsd-index-section"><h3 class="tsd-index-heading">Properties</h3><div class="tsd-index-list"><a href="#apikey" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24" aria-label="Property"><use href="../assets/icons.svg#icon-1024"></use></svg><span>api<wbr/>Key</span></a>
|
|
3
|
-
</div></section></div></details></section></section><details class="tsd-panel-group tsd-member-group tsd-accordion" open><summary class="tsd-accordion-summary" data-key="section-Properties"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h2>Properties</h2></summary><section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="apikey"><span>api<wbr/>Key</span><a href="#apikey" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">apiKey</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/enerage/LinkedInSecretSauce/blob/142a9d0b85e83a724e9d23b89f1efc205b5a6dbd/src/enrichment/types.ts#L217">src/enrichment/types.ts:217</a></li></ul></aside></section></section></details></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><details open class="tsd-accordion tsd-page-navigation-section"><summary class="tsd-accordion-summary" data-key="section-Properties"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>Properties</summary><div><a href="#apikey"><svg class="tsd-kind-icon" viewBox="0 0 24 24" aria-label="Property"><use href="../assets/icons.svg#icon-1024"></use></svg><span>api<wbr/>Key</span></a></div></details></div></details></div><div class="site-menu"><nav id="tsd-sidebar-links" class="tsd-navigation"><a href="https://github.com/enerage/LinkedInSecretSauce" class="tsd-nav-link">GitHub</a><a href="https://www.npmjs.com/package/linkedin-secret-sauce" class="tsd-nav-link">npm</a></nav><nav class="tsd-navigation"><a href="../index.html">LinkedIn Secret Sauce - v0.12.3</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
|