linkedin-secret-sauce 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -31
- package/dist/cosiall-client.d.ts +1 -1
- package/dist/cosiall-client.js +1 -1
- package/dist/enrichment/index.d.ts +23 -2
- package/dist/enrichment/index.js +38 -22
- package/dist/enrichment/matching.d.ts +16 -2
- package/dist/enrichment/matching.js +387 -65
- package/dist/enrichment/providers/bounceban.d.ts +82 -0
- package/dist/enrichment/providers/bounceban.js +447 -0
- package/dist/enrichment/providers/bouncer.d.ts +1 -1
- package/dist/enrichment/providers/bouncer.js +19 -21
- package/dist/enrichment/providers/construct.d.ts +1 -1
- package/dist/enrichment/providers/construct.js +22 -38
- package/dist/enrichment/providers/cosiall.d.ts +1 -1
- package/dist/enrichment/providers/cosiall.js +3 -4
- package/dist/enrichment/providers/dropcontact.d.ts +15 -9
- package/dist/enrichment/providers/dropcontact.js +188 -19
- package/dist/enrichment/providers/hunter.d.ts +8 -1
- package/dist/enrichment/providers/hunter.js +52 -28
- package/dist/enrichment/providers/index.d.ts +2 -0
- package/dist/enrichment/providers/index.js +10 -1
- package/dist/enrichment/providers/ldd.d.ts +1 -10
- package/dist/enrichment/providers/ldd.js +20 -97
- package/dist/enrichment/providers/smartprospect.js +28 -48
- package/dist/enrichment/providers/snovio.d.ts +1 -1
- package/dist/enrichment/providers/snovio.js +29 -31
- package/dist/enrichment/providers/trykitt.d.ts +63 -0
- package/dist/enrichment/providers/trykitt.js +210 -0
- package/dist/enrichment/types.d.ts +220 -7
- package/dist/enrichment/types.js +16 -8
- package/dist/enrichment/utils/candidate-parser.d.ts +107 -0
- package/dist/enrichment/utils/candidate-parser.js +173 -0
- package/dist/enrichment/utils/noop-provider.d.ts +39 -0
- package/dist/enrichment/utils/noop-provider.js +37 -0
- package/dist/enrichment/utils/rate-limiter.d.ts +103 -0
- package/dist/enrichment/utils/rate-limiter.js +204 -0
- package/dist/enrichment/utils/validation.d.ts +75 -3
- package/dist/enrichment/utils/validation.js +164 -11
- package/dist/linkedin-api.d.ts +40 -1
- package/dist/linkedin-api.js +160 -27
- package/dist/types.d.ts +50 -1
- package/dist/utils/lru-cache.d.ts +105 -0
- package/dist/utils/lru-cache.js +175 -0
- package/docs/COSIALL_PROFILE_EMAILS.md +342 -0
- package/docs/ENRICHMENT.md +622 -0
- package/docs/INTEGRATION.md +405 -0
- package/docs/PLAYGROUND.md +558 -0
- package/docs/SALES_SEARCH.md +171 -0
- package/docs/api/.nojekyll +1 -0
- package/docs/api/assets/hierarchy.js +1 -0
- package/docs/api/assets/highlight.css +92 -0
- package/docs/api/assets/icons.js +18 -0
- package/docs/api/assets/icons.svg +1 -0
- package/docs/api/assets/main.js +60 -0
- package/docs/api/assets/navigation.js +1 -0
- package/docs/api/assets/search.js +1 -0
- package/docs/api/assets/style.css +1633 -0
- package/docs/api/classes/LinkedInClientError.html +37 -0
- package/docs/api/functions/_testGetAccountCookies.html +4 -0
- package/docs/api/functions/_testGetAccountEntry.html +4 -0
- package/docs/api/functions/_testGetAllAccountIds.html +3 -0
- package/docs/api/functions/_testGetPoolState.html +3 -0
- package/docs/api/functions/adminResetAccount.html +1 -0
- package/docs/api/functions/adminSetCooldown.html +1 -0
- package/docs/api/functions/buildCookieHeader.html +1 -0
- package/docs/api/functions/clearAllSmartLeadTokens.html +2 -0
- package/docs/api/functions/clearRequestHistory.html +1 -0
- package/docs/api/functions/clearSessionAccount.html +1 -0
- package/docs/api/functions/clearSmartLeadToken.html +2 -0
- package/docs/api/functions/createEnrichmentClient.html +8 -0
- package/docs/api/functions/extractCsrfToken.html +1 -0
- package/docs/api/functions/extractLinkedInHandle.html +7 -0
- package/docs/api/functions/fetchCookiesFromCosiall.html +14 -0
- package/docs/api/functions/fetchProfileEmailsFromCosiall.html +18 -0
- package/docs/api/functions/forceRefreshCookies.html +1 -0
- package/docs/api/functions/getAccountForSession.html +1 -0
- package/docs/api/functions/getAccountsSummary.html +1 -0
- package/docs/api/functions/getCompaniesBatch.html +5 -0
- package/docs/api/functions/getCompanyById.html +9 -0
- package/docs/api/functions/getCompanyByUrl.html +1 -0
- package/docs/api/functions/getConfig.html +1 -0
- package/docs/api/functions/getCookiePoolHealth.html +1 -0
- package/docs/api/functions/getProfileByUrn.html +17 -0
- package/docs/api/functions/getProfileByVanity.html +10 -0
- package/docs/api/functions/getProfilesBatch.html +1 -0
- package/docs/api/functions/getRequestHistory.html +1 -0
- package/docs/api/functions/getSalesNavigatorProfileDetails.html +1 -0
- package/docs/api/functions/getSalesNavigatorProfileFull.html +16 -0
- package/docs/api/functions/getSmartLeadToken.html +1 -0
- package/docs/api/functions/getSmartLeadTokenCacheStats.html +2 -0
- package/docs/api/functions/getSmartLeadUser.html +2 -0
- package/docs/api/functions/getSnapshot.html +1 -0
- package/docs/api/functions/getYearsAtCompanyOptions.html +2 -0
- package/docs/api/functions/getYearsInPositionOptions.html +2 -0
- package/docs/api/functions/getYearsOfExperienceOptions.html +2 -0
- package/docs/api/functions/incrementMetric.html +1 -0
- package/docs/api/functions/initializeCookiePool.html +1 -0
- package/docs/api/functions/initializeLinkedInClient.html +1 -0
- package/docs/api/functions/isBusinessEmail.html +4 -0
- package/docs/api/functions/isDisposableDomain.html +4 -0
- package/docs/api/functions/isDisposableEmail.html +4 -0
- package/docs/api/functions/isPersonalDomain.html +4 -0
- package/docs/api/functions/isPersonalEmail.html +4 -0
- package/docs/api/functions/isRoleAccount.html +4 -0
- package/docs/api/functions/isValidEmailSyntax.html +4 -0
- package/docs/api/functions/parseFullProfile.html +15 -0
- package/docs/api/functions/parseSalesSearchResults.html +1 -0
- package/docs/api/functions/reportAccountFailure.html +1 -0
- package/docs/api/functions/reportAccountSuccess.html +1 -0
- package/docs/api/functions/resolveCompanyUniversalName.html +1 -0
- package/docs/api/functions/searchSalesLeads.html +16 -0
- package/docs/api/functions/selectAccountForRequest.html +1 -0
- package/docs/api/functions/setAccountForSession.html +1 -0
- package/docs/api/functions/typeahead.html +1 -0
- package/docs/api/functions/verifyEmailMx.html +1 -0
- package/docs/api/hierarchy.html +1 -0
- package/docs/api/index.html +12 -0
- package/docs/api/interfaces/AccountCookies.html +4 -0
- package/docs/api/interfaces/BatchEnrichmentOptions.html +14 -0
- package/docs/api/interfaces/CacheAdapter.html +6 -0
- package/docs/api/interfaces/CanonicalEmail.html +14 -0
- package/docs/api/interfaces/Company.html +17 -0
- package/docs/api/interfaces/ConstructConfig.html +8 -0
- package/docs/api/interfaces/CosiallProfileEmailsResponse.html +11 -0
- package/docs/api/interfaces/DropcontactConfig.html +3 -0
- package/docs/api/interfaces/EnrichmentCandidate.html +34 -0
- package/docs/api/interfaces/EnrichmentClient.html +10 -0
- package/docs/api/interfaces/EnrichmentClientConfig.html +12 -0
- package/docs/api/interfaces/EnrichmentLogger.html +6 -0
- package/docs/api/interfaces/EnrichmentOptions.html +10 -0
- package/docs/api/interfaces/HunterConfig.html +3 -0
- package/docs/api/interfaces/LddConfig.html +4 -0
- package/docs/api/interfaces/LddProfileData.html +6 -0
- package/docs/api/interfaces/LinkedInClientConfig.html +20 -0
- package/docs/api/interfaces/LinkedInCookie.html +9 -0
- package/docs/api/interfaces/LinkedInPosition.html +14 -0
- package/docs/api/interfaces/LinkedInProfile.html +21 -0
- package/docs/api/interfaces/LinkedInSpotlightBadge.html +5 -0
- package/docs/api/interfaces/LinkedInTenure.html +3 -0
- package/docs/api/interfaces/Metrics.html +22 -0
- package/docs/api/interfaces/MetricsSnapshot.html +23 -0
- package/docs/api/interfaces/ProfileEducation.html +8 -0
- package/docs/api/interfaces/ProfileEmailsLookupOptions.html +9 -0
- package/docs/api/interfaces/ProfilePosition.html +12 -0
- package/docs/api/interfaces/ProfileSkill.html +3 -0
- package/docs/api/interfaces/ProviderResult.html +11 -0
- package/docs/api/interfaces/ProvidersConfig.html +17 -0
- package/docs/api/interfaces/RequestHistoryEntry.html +8 -0
- package/docs/api/interfaces/SalesLeadSearchResult.html +31 -0
- package/docs/api/interfaces/SalesNavigatorContactInfo.html +5 -0
- package/docs/api/interfaces/SalesNavigatorPosition.html +11 -0
- package/docs/api/interfaces/SalesNavigatorProfile.html +9 -0
- package/docs/api/interfaces/SalesNavigatorProfileFull.html +24 -0
- package/docs/api/interfaces/SearchSalesResult.html +5 -0
- package/docs/api/interfaces/SmartLeadAuthConfig.html +6 -0
- package/docs/api/interfaces/SmartLeadCredentials.html +3 -0
- package/docs/api/interfaces/SmartLeadLoginResponse.html +3 -0
- package/docs/api/interfaces/SmartLeadUser.html +8 -0
- package/docs/api/interfaces/SmartProspectConfig.html +19 -0
- package/docs/api/interfaces/SmartProspectContact.html +24 -0
- package/docs/api/interfaces/SmartProspectSearchFilters.html +42 -0
- package/docs/api/interfaces/TypeaheadItem.html +3 -0
- package/docs/api/interfaces/TypeaheadResult.html +3 -0
- package/docs/api/interfaces/VerificationResult.html +16 -0
- package/docs/api/types/CostCallback.html +2 -0
- package/docs/api/types/Geo.html +4 -0
- package/docs/api/types/LddApiResponse.html +1 -0
- package/docs/api/types/ProviderFunc.html +2 -0
- package/docs/api/types/ProviderName.html +2 -0
- package/docs/api/types/SalesSearchFilters.html +7 -0
- package/docs/api/types/TypeaheadType.html +1 -0
- package/docs/api/variables/COMPANY_SIZE_OPTIONS.html +1 -0
- package/docs/api/variables/DEFAULT_PROVIDER_ORDER.html +20 -0
- package/docs/api/variables/DISPOSABLE_DOMAINS.html +2 -0
- package/docs/api/variables/FUNCTION_OPTIONS.html +1 -0
- package/docs/api/variables/INDUSTRY_OPTIONS.html +1 -0
- package/docs/api/variables/LANGUAGE_OPTIONS.html +1 -0
- package/docs/api/variables/PERSONAL_DOMAINS.html +2 -0
- package/docs/api/variables/PROVIDER_COSTS.html +15 -0
- package/docs/api/variables/REGION_OPTIONS.html +1 -0
- package/docs/api/variables/SENIORITY_OPTIONS.html +3 -0
- package/docs/api/variables/YEARS_OPTIONS.html +1 -0
- package/docs/index.html +98 -0
- package/package.json +16 -5
|
@@ -10,87 +10,14 @@
|
|
|
10
10
|
* while usernames can be changed by users at any time.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.extractNumericLinkedInId =
|
|
13
|
+
exports.extractNumericLinkedInId = void 0;
|
|
14
14
|
exports.createLddProvider = createLddProvider;
|
|
15
|
-
const validation_1 = require("../utils/validation");
|
|
16
15
|
const http_retry_1 = require("../utils/http-retry");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* - objectUrn: "urn:li:member:307567"
|
|
23
|
-
*
|
|
24
|
-
* Returns the numeric ID as a string, or null if not found.
|
|
25
|
-
*/
|
|
26
|
-
function extractNumericLinkedInId(input) {
|
|
27
|
-
if (!input)
|
|
28
|
-
return null;
|
|
29
|
-
const trimmed = input.trim();
|
|
30
|
-
// Direct numeric ID
|
|
31
|
-
if (/^\d+$/.test(trimmed)) {
|
|
32
|
-
return trimmed;
|
|
33
|
-
}
|
|
34
|
-
// URN format: urn:li:member:307567
|
|
35
|
-
const memberMatch = trimmed.match(/urn:li:member:(\d+)/i);
|
|
36
|
-
if (memberMatch) {
|
|
37
|
-
return memberMatch[1];
|
|
38
|
-
}
|
|
39
|
-
// Sales profile URN with numeric ID
|
|
40
|
-
const salesMatch = trimmed.match(/urn:li:fs_salesProfile:\((\d+),/i);
|
|
41
|
-
if (salesMatch) {
|
|
42
|
-
return salesMatch[1];
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Extract LinkedIn username from candidate
|
|
48
|
-
*/
|
|
49
|
-
function extractUsername(candidate) {
|
|
50
|
-
// Direct username
|
|
51
|
-
const directUsername = candidate.linkedinUsername || candidate.linkedin_username;
|
|
52
|
-
if (directUsername)
|
|
53
|
-
return directUsername;
|
|
54
|
-
// Extract from URL
|
|
55
|
-
const url = candidate.linkedinUrl || candidate.linkedin_url;
|
|
56
|
-
if (url) {
|
|
57
|
-
const extracted = (0, validation_1.extractLinkedInUsername)(url);
|
|
58
|
-
if (extracted)
|
|
59
|
-
return extracted;
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Extract numeric LinkedIn ID from candidate.
|
|
65
|
-
* Checks multiple fields in order of preference:
|
|
66
|
-
* 1. numericLinkedInId / numeric_linkedin_id (direct numeric ID)
|
|
67
|
-
* 2. objectUrn / object_urn (URN format like "urn:li:member:307567")
|
|
68
|
-
* 3. linkedinId / linkedin_id (may contain URN or numeric ID)
|
|
69
|
-
*/
|
|
70
|
-
function extractNumericId(candidate) {
|
|
71
|
-
// Direct numeric ID field
|
|
72
|
-
const numericId = candidate.numericLinkedInId || candidate.numeric_linkedin_id;
|
|
73
|
-
if (numericId) {
|
|
74
|
-
const extracted = extractNumericLinkedInId(numericId);
|
|
75
|
-
if (extracted)
|
|
76
|
-
return extracted;
|
|
77
|
-
}
|
|
78
|
-
// objectUrn field (from Sales Navigator search results)
|
|
79
|
-
const objectUrn = candidate.objectUrn || candidate.object_urn;
|
|
80
|
-
if (objectUrn) {
|
|
81
|
-
const extracted = extractNumericLinkedInId(objectUrn);
|
|
82
|
-
if (extracted)
|
|
83
|
-
return extracted;
|
|
84
|
-
}
|
|
85
|
-
// linkedinId field (may contain URN or numeric ID)
|
|
86
|
-
const linkedinId = candidate.linkedinId || candidate.linkedin_id;
|
|
87
|
-
if (linkedinId) {
|
|
88
|
-
const extracted = extractNumericLinkedInId(linkedinId);
|
|
89
|
-
if (extracted)
|
|
90
|
-
return extracted;
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
16
|
+
const candidate_parser_1 = require("../utils/candidate-parser");
|
|
17
|
+
const noop_provider_1 = require("../utils/noop-provider");
|
|
18
|
+
// Re-export for backwards compatibility
|
|
19
|
+
var candidate_parser_2 = require("../utils/candidate-parser");
|
|
20
|
+
Object.defineProperty(exports, "extractNumericLinkedInId", { enumerable: true, get: function () { return candidate_parser_2.extractNumericLinkedInId; } });
|
|
94
21
|
/**
|
|
95
22
|
* Create the LDD provider function
|
|
96
23
|
*
|
|
@@ -101,23 +28,19 @@ function extractNumericId(candidate) {
|
|
|
101
28
|
function createLddProvider(config) {
|
|
102
29
|
const { apiUrl, apiToken } = config;
|
|
103
30
|
if (!apiUrl || !apiToken) {
|
|
104
|
-
|
|
105
|
-
const noopProvider = async () => null;
|
|
106
|
-
noopProvider.__name = "ldd";
|
|
107
|
-
return noopProvider;
|
|
31
|
+
return (0, noop_provider_1.createNoOpProvider)("ldd");
|
|
108
32
|
}
|
|
109
33
|
async function fetchEmail(candidate) {
|
|
34
|
+
const linkedin = (0, candidate_parser_1.extractLinkedIn)(candidate);
|
|
110
35
|
// PRIORITY 1: Try numeric ID first (stable identifier)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const result = await lookupByNumericId(numericId);
|
|
36
|
+
if (linkedin.numericId) {
|
|
37
|
+
const result = await lookupByNumericId(linkedin.numericId);
|
|
114
38
|
if (result)
|
|
115
39
|
return result;
|
|
116
40
|
}
|
|
117
41
|
// PRIORITY 2: Fall back to username (less reliable)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const result = await lookupByUsername(username);
|
|
42
|
+
if (linkedin.username) {
|
|
43
|
+
const result = await lookupByUsername(linkedin.username);
|
|
121
44
|
if (result)
|
|
122
45
|
return result;
|
|
123
46
|
}
|
|
@@ -152,11 +75,11 @@ function createLddProvider(config) {
|
|
|
152
75
|
return 75; // Unknown type
|
|
153
76
|
const type = emailType.toLowerCase();
|
|
154
77
|
// Professional/business emails - highest confidence
|
|
155
|
-
if (type ===
|
|
78
|
+
if (type === "professional" || type === "business" || type === "work") {
|
|
156
79
|
return 95;
|
|
157
80
|
}
|
|
158
81
|
// Personal emails - still valid but lower priority for B2B
|
|
159
|
-
if (type ===
|
|
82
|
+
if (type === "personal" || type === "private") {
|
|
160
83
|
return 70;
|
|
161
84
|
}
|
|
162
85
|
// Other types (e.g., "other", "unknown")
|
|
@@ -167,15 +90,15 @@ function createLddProvider(config) {
|
|
|
167
90
|
*/
|
|
168
91
|
function classifyEmailType(emailType) {
|
|
169
92
|
if (!emailType)
|
|
170
|
-
return
|
|
93
|
+
return "unknown";
|
|
171
94
|
const type = emailType.toLowerCase();
|
|
172
|
-
if (type ===
|
|
173
|
-
return
|
|
95
|
+
if (type === "professional" || type === "business" || type === "work") {
|
|
96
|
+
return "business";
|
|
174
97
|
}
|
|
175
|
-
if (type ===
|
|
176
|
-
return
|
|
98
|
+
if (type === "personal" || type === "private") {
|
|
99
|
+
return "personal";
|
|
177
100
|
}
|
|
178
|
-
return
|
|
101
|
+
return "unknown";
|
|
179
102
|
}
|
|
180
103
|
function parseResponse(data) {
|
|
181
104
|
if (!data.success || !data.data) {
|
|
@@ -18,6 +18,8 @@ exports.createSmartProspectProvider = createSmartProspectProvider;
|
|
|
18
18
|
exports.createSmartProspectClient = createSmartProspectClient;
|
|
19
19
|
const smartlead_auth_1 = require("../auth/smartlead-auth");
|
|
20
20
|
const http_retry_1 = require("../utils/http-retry");
|
|
21
|
+
const candidate_parser_1 = require("../utils/candidate-parser");
|
|
22
|
+
const noop_provider_1 = require("../utils/noop-provider");
|
|
21
23
|
const DEFAULT_API_URL = "https://prospect-api.smartlead.ai/api/search-email-leads";
|
|
22
24
|
const DEFAULT_POLLING_CONFIG = {
|
|
23
25
|
initialDelay: 500,
|
|
@@ -96,22 +98,6 @@ function calculateMatchScore(contact, searchName, company, title) {
|
|
|
96
98
|
}
|
|
97
99
|
return score;
|
|
98
100
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Extract name components from candidate
|
|
101
|
-
*/
|
|
102
|
-
function extractNames(candidate) {
|
|
103
|
-
const firstName = candidate.firstName ||
|
|
104
|
-
candidate.first_name ||
|
|
105
|
-
candidate.first ||
|
|
106
|
-
candidate.name?.split(" ")?.[0] ||
|
|
107
|
-
"";
|
|
108
|
-
const lastName = candidate.lastName ||
|
|
109
|
-
candidate.last_name ||
|
|
110
|
-
candidate.last ||
|
|
111
|
-
candidate.name?.split(" ")?.slice(1).join(" ") ||
|
|
112
|
-
"";
|
|
113
|
-
return { firstName, lastName };
|
|
114
|
-
}
|
|
115
101
|
/**
|
|
116
102
|
* Create the SmartProspect provider function
|
|
117
103
|
*
|
|
@@ -125,10 +111,7 @@ function createSmartProspectProvider(config) {
|
|
|
125
111
|
const hasDirectToken = !!apiToken;
|
|
126
112
|
const hasCredentials = !!email && !!password;
|
|
127
113
|
if (!hasDirectToken && !hasCredentials) {
|
|
128
|
-
|
|
129
|
-
const noopProvider = async () => null;
|
|
130
|
-
noopProvider.__name = "smartprospect";
|
|
131
|
-
return noopProvider;
|
|
114
|
+
return (0, noop_provider_1.createNoOpProvider)("smartprospect");
|
|
132
115
|
}
|
|
133
116
|
/**
|
|
134
117
|
* Get the current auth token (either direct or via login)
|
|
@@ -241,7 +224,9 @@ function createSmartProspectProvider(config) {
|
|
|
241
224
|
await (0, http_retry_1.delay)(pollInterval);
|
|
242
225
|
}
|
|
243
226
|
catch (err) {
|
|
244
|
-
if (err instanceof Error &&
|
|
227
|
+
if (err instanceof Error &&
|
|
228
|
+
err.message.includes("401") &&
|
|
229
|
+
hasCredentials) {
|
|
245
230
|
await handleAuthError();
|
|
246
231
|
}
|
|
247
232
|
await (0, http_retry_1.delay)(pollInterval);
|
|
@@ -372,23 +357,12 @@ function createSmartProspectProvider(config) {
|
|
|
372
357
|
return confidence;
|
|
373
358
|
}
|
|
374
359
|
async function fetchEmail(candidate) {
|
|
375
|
-
const { firstName,
|
|
360
|
+
const { firstName, fullName } = (0, candidate_parser_1.extractName)(candidate);
|
|
376
361
|
if (!firstName) {
|
|
377
362
|
return null; // Minimum requirement
|
|
378
363
|
}
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
candidate.currentCompany ||
|
|
382
|
-
candidate.organization ||
|
|
383
|
-
undefined;
|
|
384
|
-
const title = candidate.title ||
|
|
385
|
-
candidate.currentRole ||
|
|
386
|
-
candidate.current_role ||
|
|
387
|
-
undefined;
|
|
388
|
-
const domain = candidate.domain ||
|
|
389
|
-
candidate.companyDomain ||
|
|
390
|
-
candidate.company_domain ||
|
|
391
|
-
undefined;
|
|
364
|
+
const { company, domain } = (0, candidate_parser_1.extractCompany)(candidate);
|
|
365
|
+
const title = (0, candidate_parser_1.extractTitle)(candidate) ?? undefined;
|
|
392
366
|
// Build search filters using correct API field names
|
|
393
367
|
const filters = {
|
|
394
368
|
name: [fullName],
|
|
@@ -410,7 +384,7 @@ function createSmartProspectProvider(config) {
|
|
|
410
384
|
const scoredMatches = matches
|
|
411
385
|
.map((contact) => ({
|
|
412
386
|
contact,
|
|
413
|
-
score: calculateMatchScore(contact, fullName, company, title),
|
|
387
|
+
score: calculateMatchScore(contact, fullName, company ?? undefined, title),
|
|
414
388
|
}))
|
|
415
389
|
.filter((m) => m.score > 0) // Only include matches with some relevance
|
|
416
390
|
.sort((a, b) => b.score - a.score);
|
|
@@ -547,20 +521,26 @@ function createSmartProspectClient(config) {
|
|
|
547
521
|
if (filters.level?.length)
|
|
548
522
|
payload.level = filters.level;
|
|
549
523
|
// Company filters (note: API uses companyName and companyDomain)
|
|
550
|
-
if (filters.companyName?.length)
|
|
524
|
+
if (filters.companyName?.length) {
|
|
551
525
|
payload.companyName = filters.companyName;
|
|
552
|
-
|
|
526
|
+
}
|
|
527
|
+
if (filters.companyDomain?.length) {
|
|
553
528
|
payload.companyDomain = filters.companyDomain;
|
|
529
|
+
}
|
|
554
530
|
// Industry filters (note: API uses companyIndustry and companySubIndustry)
|
|
555
|
-
if (filters.companyIndustry?.length)
|
|
531
|
+
if (filters.companyIndustry?.length) {
|
|
556
532
|
payload.companyIndustry = filters.companyIndustry;
|
|
557
|
-
|
|
533
|
+
}
|
|
534
|
+
if (filters.companySubIndustry?.length) {
|
|
558
535
|
payload.companySubIndustry = filters.companySubIndustry;
|
|
536
|
+
}
|
|
559
537
|
// Company size filters
|
|
560
|
-
if (filters.companyHeadCount?.length)
|
|
538
|
+
if (filters.companyHeadCount?.length) {
|
|
561
539
|
payload.companyHeadCount = filters.companyHeadCount;
|
|
562
|
-
|
|
540
|
+
}
|
|
541
|
+
if (filters.companyRevenue?.length) {
|
|
563
542
|
payload.companyRevenue = filters.companyRevenue;
|
|
543
|
+
}
|
|
564
544
|
// Location filters
|
|
565
545
|
if (filters.country?.length)
|
|
566
546
|
payload.country = filters.country;
|
|
@@ -826,11 +806,11 @@ function createSmartProspectClient(config) {
|
|
|
826
806
|
async function getLocations(type, options = {}) {
|
|
827
807
|
const { search: searchQuery, limit = 20, offset = 0 } = options;
|
|
828
808
|
const params = new URLSearchParams();
|
|
829
|
-
params.set(
|
|
809
|
+
params.set("limit", String(limit));
|
|
830
810
|
if (offset > 0)
|
|
831
|
-
params.set(
|
|
811
|
+
params.set("offset", String(offset));
|
|
832
812
|
if (searchQuery)
|
|
833
|
-
params.set(
|
|
813
|
+
params.set("search", searchQuery);
|
|
834
814
|
const makeRequest = async (token) => {
|
|
835
815
|
return requestWithRetry(`${apiUrl}/${type}?${params.toString()}`, {
|
|
836
816
|
method: "GET",
|
|
@@ -870,19 +850,19 @@ function createSmartProspectClient(config) {
|
|
|
870
850
|
* Lookup countries (for typeahead)
|
|
871
851
|
*/
|
|
872
852
|
async function getCountries(options) {
|
|
873
|
-
return getLocations(
|
|
853
|
+
return getLocations("countries", options);
|
|
874
854
|
}
|
|
875
855
|
/**
|
|
876
856
|
* Lookup states (for typeahead)
|
|
877
857
|
*/
|
|
878
858
|
async function getStates(options) {
|
|
879
|
-
return getLocations(
|
|
859
|
+
return getLocations("states", options);
|
|
880
860
|
}
|
|
881
861
|
/**
|
|
882
862
|
* Lookup cities (for typeahead)
|
|
883
863
|
*/
|
|
884
864
|
async function getCities(options) {
|
|
885
|
-
return getLocations(
|
|
865
|
+
return getLocations("cities", options);
|
|
886
866
|
}
|
|
887
867
|
return {
|
|
888
868
|
search,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @see https://snov.io/api
|
|
14
14
|
*/
|
|
15
|
-
import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, SnovioConfig, SnovioGetEmailsResponse, SnovioVerificationStatus } from
|
|
15
|
+
import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, SnovioConfig, SnovioGetEmailsResponse, SnovioVerificationStatus } from "../types";
|
|
16
16
|
/**
|
|
17
17
|
* Create the Snov.io email finder provider
|
|
18
18
|
*
|
|
@@ -18,7 +18,8 @@ exports.createSnovioProvider = createSnovioProvider;
|
|
|
18
18
|
exports.findEmailsWithSnovio = findEmailsWithSnovio;
|
|
19
19
|
exports.verifyEmailWithSnovio = verifyEmailWithSnovio;
|
|
20
20
|
exports.clearSnovioTokenCache = clearSnovioTokenCache;
|
|
21
|
-
const
|
|
21
|
+
const noop_provider_1 = require("../utils/noop-provider");
|
|
22
|
+
const DEFAULT_API_URL = "https://api.snov.io";
|
|
22
23
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
23
24
|
// Token cache
|
|
24
25
|
let cachedAccessToken = null;
|
|
@@ -28,15 +29,15 @@ let tokenExpiresAt = 0;
|
|
|
28
29
|
*/
|
|
29
30
|
function statusToConfidence(status) {
|
|
30
31
|
switch (status) {
|
|
31
|
-
case
|
|
32
|
+
case "valid":
|
|
32
33
|
return 95; // High confidence - verified email
|
|
33
|
-
case
|
|
34
|
+
case "catch_all":
|
|
34
35
|
return 60; // Medium confidence - catch-all domain
|
|
35
|
-
case
|
|
36
|
+
case "unverifiable":
|
|
36
37
|
return 40; // Low confidence - could not verify
|
|
37
|
-
case
|
|
38
|
+
case "not_valid":
|
|
38
39
|
return 0; // Invalid email
|
|
39
|
-
case
|
|
40
|
+
case "unknown":
|
|
40
41
|
default:
|
|
41
42
|
return 30;
|
|
42
43
|
}
|
|
@@ -48,17 +49,17 @@ function extractNameAndDomain(candidate) {
|
|
|
48
49
|
const firstName = candidate.firstName ||
|
|
49
50
|
candidate.first_name ||
|
|
50
51
|
candidate.first ||
|
|
51
|
-
candidate.name?.split(
|
|
52
|
-
|
|
52
|
+
candidate.name?.split(" ")?.[0] ||
|
|
53
|
+
"";
|
|
53
54
|
const lastName = candidate.lastName ||
|
|
54
55
|
candidate.last_name ||
|
|
55
56
|
candidate.last ||
|
|
56
|
-
candidate.name?.split(
|
|
57
|
-
|
|
57
|
+
candidate.name?.split(" ")?.slice(1).join(" ") ||
|
|
58
|
+
"";
|
|
58
59
|
const domain = candidate.domain ||
|
|
59
60
|
candidate.companyDomain ||
|
|
60
61
|
candidate.company_domain ||
|
|
61
|
-
|
|
62
|
+
"";
|
|
62
63
|
if (!firstName || !domain) {
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
@@ -76,12 +77,12 @@ async function getAccessToken(userId, apiSecret, apiUrl, timeoutMs) {
|
|
|
76
77
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
77
78
|
try {
|
|
78
79
|
const response = await fetch(`${apiUrl}/v1/oauth/access_token`, {
|
|
79
|
-
method:
|
|
80
|
+
method: "POST",
|
|
80
81
|
headers: {
|
|
81
|
-
|
|
82
|
+
"Content-Type": "application/json",
|
|
82
83
|
},
|
|
83
84
|
body: JSON.stringify({
|
|
84
|
-
grant_type:
|
|
85
|
+
grant_type: "client_credentials",
|
|
85
86
|
client_id: userId,
|
|
86
87
|
client_secret: apiSecret,
|
|
87
88
|
}),
|
|
@@ -90,7 +91,7 @@ async function getAccessToken(userId, apiSecret, apiUrl, timeoutMs) {
|
|
|
90
91
|
if (!response.ok) {
|
|
91
92
|
return null;
|
|
92
93
|
}
|
|
93
|
-
const data = await response.json();
|
|
94
|
+
const data = (await response.json());
|
|
94
95
|
// Cache token with 5 minute buffer
|
|
95
96
|
cachedAccessToken = data.access_token;
|
|
96
97
|
tokenExpiresAt = Date.now() + (data.expires_in - 300) * 1000;
|
|
@@ -117,24 +118,24 @@ async function findEmailsByName(firstName, lastName, domain, accessToken, apiUrl
|
|
|
117
118
|
domain,
|
|
118
119
|
});
|
|
119
120
|
const response = await fetch(`${apiUrl}/v1/get-emails-from-names?${params}`, {
|
|
120
|
-
method:
|
|
121
|
+
method: "GET",
|
|
121
122
|
headers: {
|
|
122
|
-
|
|
123
|
+
Accept: "application/json",
|
|
123
124
|
},
|
|
124
125
|
signal: controller.signal,
|
|
125
126
|
});
|
|
126
127
|
if (!response.ok) {
|
|
127
128
|
// Handle rate limiting
|
|
128
129
|
if (response.status === 429) {
|
|
129
|
-
throw new Error(
|
|
130
|
+
throw new Error("Rate limited by Snov.io API");
|
|
130
131
|
}
|
|
131
132
|
return null;
|
|
132
133
|
}
|
|
133
|
-
const data = await response.json();
|
|
134
|
+
const data = (await response.json());
|
|
134
135
|
return data;
|
|
135
136
|
}
|
|
136
137
|
catch (error) {
|
|
137
|
-
if (error instanceof Error && error.name ===
|
|
138
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
138
139
|
return null; // Timeout
|
|
139
140
|
}
|
|
140
141
|
throw error;
|
|
@@ -152,10 +153,7 @@ async function findEmailsByName(firstName, lastName, domain, accessToken, apiUrl
|
|
|
152
153
|
function createSnovioProvider(config) {
|
|
153
154
|
const { userId, apiSecret, apiUrl = DEFAULT_API_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = config;
|
|
154
155
|
if (!userId || !apiSecret) {
|
|
155
|
-
|
|
156
|
-
const noop = async () => null;
|
|
157
|
-
noop.__name = 'snovio';
|
|
158
|
-
return noop;
|
|
156
|
+
return (0, noop_provider_1.createNoOpProvider)("snovio");
|
|
159
157
|
}
|
|
160
158
|
async function findEmails(candidate) {
|
|
161
159
|
const nameAndDomain = extractNameAndDomain(candidate);
|
|
@@ -175,12 +173,12 @@ function createSnovioProvider(config) {
|
|
|
175
173
|
}
|
|
176
174
|
// Convert to provider multi-result format
|
|
177
175
|
const emails = result.data.emails
|
|
178
|
-
.filter((e) => e.email && e.emailStatus !==
|
|
176
|
+
.filter((e) => e.email && e.emailStatus !== "not_valid")
|
|
179
177
|
.map((e) => ({
|
|
180
178
|
email: e.email,
|
|
181
|
-
verified: e.emailStatus ===
|
|
179
|
+
verified: e.emailStatus === "valid",
|
|
182
180
|
confidence: statusToConfidence(e.emailStatus),
|
|
183
|
-
isCatchAll: e.emailStatus ===
|
|
181
|
+
isCatchAll: e.emailStatus === "catch_all",
|
|
184
182
|
metadata: {
|
|
185
183
|
snovioStatus: e.emailStatus,
|
|
186
184
|
firstName: e.firstName,
|
|
@@ -199,7 +197,7 @@ function createSnovioProvider(config) {
|
|
|
199
197
|
return { emails };
|
|
200
198
|
}
|
|
201
199
|
// Mark provider name for orchestrator
|
|
202
|
-
findEmails.__name =
|
|
200
|
+
findEmails.__name = "snovio";
|
|
203
201
|
return findEmails;
|
|
204
202
|
}
|
|
205
203
|
/**
|
|
@@ -251,16 +249,16 @@ async function verifyEmailWithSnovio(email, config) {
|
|
|
251
249
|
email,
|
|
252
250
|
});
|
|
253
251
|
const response = await fetch(`${apiUrl}/v1/email-verifier?${params}`, {
|
|
254
|
-
method:
|
|
252
|
+
method: "GET",
|
|
255
253
|
headers: {
|
|
256
|
-
|
|
254
|
+
Accept: "application/json",
|
|
257
255
|
},
|
|
258
256
|
signal: controller.signal,
|
|
259
257
|
});
|
|
260
258
|
if (!response.ok) {
|
|
261
259
|
return null;
|
|
262
260
|
}
|
|
263
|
-
const data = await response.json();
|
|
261
|
+
const data = (await response.json());
|
|
264
262
|
if (!data.success || !data.data?.emails?.length) {
|
|
265
263
|
return null;
|
|
266
264
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TryKitt.ai Email Finder Provider
|
|
3
|
+
*
|
|
4
|
+
* AI-powered email finding with enterprise identity server verification.
|
|
5
|
+
* FREE for individuals with unlimited searches.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Email finding by name + domain
|
|
9
|
+
* - Enterprise identity server catch-all verification (more accurate than SMTP)
|
|
10
|
+
* - <0.1% bounce rate claimed
|
|
11
|
+
* - 2-5x faster than traditional SMTP verification
|
|
12
|
+
*
|
|
13
|
+
* API Endpoints:
|
|
14
|
+
* - POST /job/find-email - Find email by name + domain
|
|
15
|
+
* - POST /job/verify-email - Verify existing email
|
|
16
|
+
*
|
|
17
|
+
* Rate Limits (Free tier):
|
|
18
|
+
* - 2 requests/second
|
|
19
|
+
* - 120 requests/minute
|
|
20
|
+
* - Unlimited monthly quota
|
|
21
|
+
*
|
|
22
|
+
* @see https://trykitt.ai
|
|
23
|
+
*/
|
|
24
|
+
import type { EnrichmentCandidate, ProviderResult, TryKittConfig, TryKittFindEmailResponse } from "../types";
|
|
25
|
+
/**
|
|
26
|
+
* Create the TryKitt.ai email finder provider
|
|
27
|
+
*
|
|
28
|
+
* This provider uses TryKitt's AI-powered email finding with enterprise
|
|
29
|
+
* identity server verification for catch-all domains.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTryKittProvider(config: TryKittConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Standalone function to find email via TryKitt.ai
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const result = await findEmailWithTryKitt('John Doe', 'example.com', {
|
|
38
|
+
* apiKey: process.env.TRYKITT_API_KEY,
|
|
39
|
+
* });
|
|
40
|
+
* console.log(result?.email); // john.doe@example.com
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function findEmailWithTryKitt(fullName: string, domain: string, config: TryKittConfig, linkedinUrl?: string): Promise<TryKittFindEmailResponse | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Verify an email address via TryKitt.ai
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const result = await verifyEmailWithTryKitt('john@example.com', {
|
|
50
|
+
* apiKey: process.env.TRYKITT_API_KEY,
|
|
51
|
+
* });
|
|
52
|
+
* console.log(result?.result?.valid); // true
|
|
53
|
+
* console.log(result?.result?.is_catchall); // false
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function verifyEmailWithTryKitt(email: string, config: TryKittConfig): Promise<{
|
|
57
|
+
valid: boolean;
|
|
58
|
+
deliverable: boolean;
|
|
59
|
+
confidence: number;
|
|
60
|
+
isCatchAll: boolean;
|
|
61
|
+
isDisposable: boolean;
|
|
62
|
+
isRoleAccount: boolean;
|
|
63
|
+
} | null>;
|