linkedin-secret-sauce 0.5.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +1 -1
- package/dist/cosiall-client.js +2 -1
- package/dist/enrichment/auth/index.d.ts +1 -1
- package/dist/enrichment/auth/index.js +6 -1
- package/dist/enrichment/auth/smartlead-auth.d.ts +32 -0
- package/dist/enrichment/auth/smartlead-auth.js +163 -0
- package/dist/enrichment/index.d.ts +5 -2
- package/dist/enrichment/index.js +46 -1
- package/dist/enrichment/matching.d.ts +241 -0
- package/dist/enrichment/matching.js +626 -0
- package/dist/enrichment/orchestrator.d.ts +13 -1
- package/dist/enrichment/orchestrator.js +222 -5
- package/dist/enrichment/providers/apollo.d.ts +2 -2
- package/dist/enrichment/providers/apollo.js +59 -14
- package/dist/enrichment/providers/construct.d.ts +2 -2
- package/dist/enrichment/providers/construct.js +16 -4
- package/dist/enrichment/providers/hunter.d.ts +2 -2
- package/dist/enrichment/providers/hunter.js +48 -22
- package/dist/enrichment/providers/ldd.d.ts +20 -2
- package/dist/enrichment/providers/ldd.js +122 -16
- package/dist/enrichment/providers/smartprospect.d.ts +64 -2
- package/dist/enrichment/providers/smartprospect.js +605 -38
- package/dist/enrichment/types.d.ts +167 -11
- package/dist/enrichment/types.js +50 -1
- package/dist/http-client.js +1 -1
- package/dist/linkedin-api.d.ts +1 -2
- package/dist/parsers/profile-parser.js +4 -2
- package/dist/utils/linkedin-config.js +1 -1
- package/dist/utils/search-encoder.js +8 -9
- package/package.json +22 -15
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* YOUR private database of ~500M scraped LinkedIn profiles with emails.
|
|
6
6
|
* This is FREE and unlimited - it's your own service.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: Prefer numeric LinkedIn ID over username for lookups.
|
|
9
|
+
* The numeric ID (from objectUrn like "urn:li:member:307567") is STABLE and never changes,
|
|
10
|
+
* while usernames can be changed by users at any time.
|
|
7
11
|
*/
|
|
8
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.extractNumericLinkedInId = extractNumericLinkedInId;
|
|
9
14
|
exports.createLddProvider = createLddProvider;
|
|
10
15
|
const validation_1 = require("../utils/validation");
|
|
11
16
|
/**
|
|
@@ -45,6 +50,35 @@ async function requestWithRetry(url, token, retries = 1, backoffMs = 200) {
|
|
|
45
50
|
}
|
|
46
51
|
throw lastErr ?? new Error("ldd_http_error");
|
|
47
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract numeric LinkedIn ID from various formats:
|
|
55
|
+
* - Direct number: "307567"
|
|
56
|
+
* - URN format: "urn:li:member:307567"
|
|
57
|
+
* - Full URN: "urn:li:fs_salesProfile:(ACwAAAAEsW8B...,NAME_SEARCH,PJOV)"
|
|
58
|
+
* - objectUrn: "urn:li:member:307567"
|
|
59
|
+
*
|
|
60
|
+
* Returns the numeric ID as a string, or null if not found.
|
|
61
|
+
*/
|
|
62
|
+
function extractNumericLinkedInId(input) {
|
|
63
|
+
if (!input)
|
|
64
|
+
return null;
|
|
65
|
+
const trimmed = input.trim();
|
|
66
|
+
// Direct numeric ID
|
|
67
|
+
if (/^\d+$/.test(trimmed)) {
|
|
68
|
+
return trimmed;
|
|
69
|
+
}
|
|
70
|
+
// URN format: urn:li:member:307567
|
|
71
|
+
const memberMatch = trimmed.match(/urn:li:member:(\d+)/i);
|
|
72
|
+
if (memberMatch) {
|
|
73
|
+
return memberMatch[1];
|
|
74
|
+
}
|
|
75
|
+
// Sales profile URN with numeric ID
|
|
76
|
+
const salesMatch = trimmed.match(/urn:li:fs_salesProfile:\((\d+),/i);
|
|
77
|
+
if (salesMatch) {
|
|
78
|
+
return salesMatch[1];
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
48
82
|
/**
|
|
49
83
|
* Extract LinkedIn username from candidate
|
|
50
84
|
*/
|
|
@@ -62,8 +96,43 @@ function extractUsername(candidate) {
|
|
|
62
96
|
}
|
|
63
97
|
return null;
|
|
64
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Extract numeric LinkedIn ID from candidate.
|
|
101
|
+
* Checks multiple fields in order of preference:
|
|
102
|
+
* 1. numericLinkedInId / numeric_linkedin_id (direct numeric ID)
|
|
103
|
+
* 2. objectUrn / object_urn (URN format like "urn:li:member:307567")
|
|
104
|
+
* 3. linkedinId / linkedin_id (may contain URN or numeric ID)
|
|
105
|
+
*/
|
|
106
|
+
function extractNumericId(candidate) {
|
|
107
|
+
// Direct numeric ID field
|
|
108
|
+
const numericId = candidate.numericLinkedInId || candidate.numeric_linkedin_id;
|
|
109
|
+
if (numericId) {
|
|
110
|
+
const extracted = extractNumericLinkedInId(numericId);
|
|
111
|
+
if (extracted)
|
|
112
|
+
return extracted;
|
|
113
|
+
}
|
|
114
|
+
// objectUrn field (from Sales Navigator search results)
|
|
115
|
+
const objectUrn = candidate.objectUrn || candidate.object_urn;
|
|
116
|
+
if (objectUrn) {
|
|
117
|
+
const extracted = extractNumericLinkedInId(objectUrn);
|
|
118
|
+
if (extracted)
|
|
119
|
+
return extracted;
|
|
120
|
+
}
|
|
121
|
+
// linkedinId field (may contain URN or numeric ID)
|
|
122
|
+
const linkedinId = candidate.linkedinId || candidate.linkedin_id;
|
|
123
|
+
if (linkedinId) {
|
|
124
|
+
const extracted = extractNumericLinkedInId(linkedinId);
|
|
125
|
+
if (extracted)
|
|
126
|
+
return extracted;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
65
130
|
/**
|
|
66
131
|
* Create the LDD provider function
|
|
132
|
+
*
|
|
133
|
+
* Lookup priority:
|
|
134
|
+
* 1. Numeric LinkedIn ID (PREFERRED - stable, never changes)
|
|
135
|
+
* 2. Username (FALLBACK - can change over time)
|
|
67
136
|
*/
|
|
68
137
|
function createLddProvider(config) {
|
|
69
138
|
const { apiUrl, apiToken } = config;
|
|
@@ -74,36 +143,73 @@ function createLddProvider(config) {
|
|
|
74
143
|
return noopProvider;
|
|
75
144
|
}
|
|
76
145
|
async function fetchEmail(candidate) {
|
|
146
|
+
// PRIORITY 1: Try numeric ID first (stable identifier)
|
|
147
|
+
const numericId = extractNumericId(candidate);
|
|
148
|
+
if (numericId) {
|
|
149
|
+
const result = await lookupByNumericId(numericId);
|
|
150
|
+
if (result)
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
// PRIORITY 2: Fall back to username (less reliable)
|
|
77
154
|
const username = extractUsername(candidate);
|
|
78
|
-
if (
|
|
79
|
-
|
|
155
|
+
if (username) {
|
|
156
|
+
const result = await lookupByUsername(username);
|
|
157
|
+
if (result)
|
|
158
|
+
return result;
|
|
80
159
|
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
async function lookupByNumericId(numericId) {
|
|
81
163
|
try {
|
|
82
|
-
const endpoint = `${apiUrl}/api/v1/profiles/by-
|
|
164
|
+
const endpoint = `${apiUrl}/api/v1/profiles/by-numeric-id/${encodeURIComponent(numericId)}`;
|
|
83
165
|
const response = await requestWithRetry(endpoint, apiToken, 1, 100);
|
|
84
166
|
if (!response.ok) {
|
|
85
167
|
return null;
|
|
86
168
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
169
|
+
return parseResponse(await response.json());
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function lookupByUsername(username) {
|
|
176
|
+
try {
|
|
177
|
+
const endpoint = `${apiUrl}/api/v1/profiles/by-username/${encodeURIComponent(username)}`;
|
|
178
|
+
const response = await requestWithRetry(endpoint, apiToken, 1, 100);
|
|
179
|
+
if (!response.ok) {
|
|
95
180
|
return null;
|
|
96
181
|
}
|
|
97
|
-
return
|
|
98
|
-
email: validEmail.email_address,
|
|
99
|
-
verified: true, // LDD data is pre-verified
|
|
100
|
-
score: 90, // High confidence from your own database
|
|
101
|
-
};
|
|
182
|
+
return parseResponse(await response.json());
|
|
102
183
|
}
|
|
103
184
|
catch {
|
|
104
185
|
return null;
|
|
105
186
|
}
|
|
106
187
|
}
|
|
188
|
+
function parseResponse(data) {
|
|
189
|
+
if (!data.success || !data.data) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
// Return ALL valid emails from the profile
|
|
193
|
+
const profileEmails = data.data.emails || [];
|
|
194
|
+
const validEmails = profileEmails.filter((e) => e.email_address && e.email_address.includes("@"));
|
|
195
|
+
if (validEmails.length === 0) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
// Build multi-result with all emails
|
|
199
|
+
const emails = validEmails.map((e) => ({
|
|
200
|
+
email: e.email_address,
|
|
201
|
+
verified: true, // LDD data is pre-verified
|
|
202
|
+
confidence: 90, // High confidence from your own database
|
|
203
|
+
metadata: {
|
|
204
|
+
emailType: e.email_type,
|
|
205
|
+
fullName: data.data.full_name,
|
|
206
|
+
linkedinUsername: data.data.linkedin_username,
|
|
207
|
+
linkedinUrl: data.data.linkedin_profile_url,
|
|
208
|
+
numericLinkedInId: data.data.linkedin_id,
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
return { emails };
|
|
212
|
+
}
|
|
107
213
|
// Mark provider name for orchestrator
|
|
108
214
|
fetchEmail.__name = "ldd";
|
|
109
215
|
return fetchEmail;
|
|
@@ -7,8 +7,41 @@
|
|
|
7
7
|
* Supports two authentication methods:
|
|
8
8
|
* 1. Direct token: Pass `apiToken` directly (for pre-authenticated scenarios)
|
|
9
9
|
* 2. Credentials: Pass `email` and `password` for automatic login with token caching
|
|
10
|
+
*
|
|
11
|
+
* Exports:
|
|
12
|
+
* - createSmartProspectProvider: For email enrichment (waterfall pattern)
|
|
13
|
+
* - createSmartProspectClient: Full client with search/fetch capabilities
|
|
14
|
+
*/
|
|
15
|
+
import type { EnrichmentCandidate, ProviderResult, ProviderMultiResult, SmartProspectConfig, SmartProspectSearchFilters, SmartProspectSearchResponse, SmartProspectFetchResponse, SmartProspectContact, SmartProspectLocationResponse } from "../types";
|
|
16
|
+
/**
|
|
17
|
+
* SmartProspect Location lookup options
|
|
10
18
|
*/
|
|
11
|
-
|
|
19
|
+
export interface SmartProspectLocationOptions {
|
|
20
|
+
search?: string;
|
|
21
|
+
limit?: number;
|
|
22
|
+
offset?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* SmartProspect Client interface for direct API access
|
|
26
|
+
*/
|
|
27
|
+
export interface SmartProspectClient {
|
|
28
|
+
/** Search for contacts (FREE - no credits used) */
|
|
29
|
+
search: (filters: SmartProspectSearchFilters) => Promise<SmartProspectSearchResponse>;
|
|
30
|
+
/** Fetch/enrich emails for specific contact IDs (COSTS CREDITS) */
|
|
31
|
+
fetch: (contactIds: string[]) => Promise<SmartProspectFetchResponse>;
|
|
32
|
+
/** Search and immediately fetch all results (COSTS CREDITS for fetched contacts) */
|
|
33
|
+
searchAndFetch: (filters: SmartProspectSearchFilters) => Promise<{
|
|
34
|
+
searchResponse: SmartProspectSearchResponse;
|
|
35
|
+
fetchResponse: SmartProspectFetchResponse | null;
|
|
36
|
+
contacts: SmartProspectContact[];
|
|
37
|
+
}>;
|
|
38
|
+
/** Lookup countries (for typeahead) */
|
|
39
|
+
getCountries: (options?: SmartProspectLocationOptions) => Promise<SmartProspectLocationResponse>;
|
|
40
|
+
/** Lookup states (for typeahead) */
|
|
41
|
+
getStates: (options?: SmartProspectLocationOptions) => Promise<SmartProspectLocationResponse>;
|
|
42
|
+
/** Lookup cities (for typeahead) */
|
|
43
|
+
getCities: (options?: SmartProspectLocationOptions) => Promise<SmartProspectLocationResponse>;
|
|
44
|
+
}
|
|
12
45
|
/**
|
|
13
46
|
* Create the SmartProspect provider function
|
|
14
47
|
*
|
|
@@ -16,4 +49,33 @@ import type { EnrichmentCandidate, ProviderResult, SmartProspectConfig } from ".
|
|
|
16
49
|
* 1. Direct token: Pass `apiToken` in config
|
|
17
50
|
* 2. Credentials: Pass `email` and `password` in config for auto-login
|
|
18
51
|
*/
|
|
19
|
-
export declare function createSmartProspectProvider(config: SmartProspectConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | null>;
|
|
52
|
+
export declare function createSmartProspectProvider(config: SmartProspectConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | ProviderMultiResult | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Create a SmartProspect client for direct API access
|
|
55
|
+
*
|
|
56
|
+
* This provides access to the full SmartProspect API for:
|
|
57
|
+
* - Searching contacts with comprehensive filters (FREE)
|
|
58
|
+
* - Fetching/enriching contact emails (COSTS CREDITS)
|
|
59
|
+
* - Combined search + fetch operations
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const client = createSmartProspectClient({
|
|
64
|
+
* email: 'user@example.com',
|
|
65
|
+
* password: 'password123'
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Search only (FREE)
|
|
69
|
+
* const results = await client.search({
|
|
70
|
+
* title: ['CEO', 'CTO'],
|
|
71
|
+
* company: ['Google', 'Microsoft'],
|
|
72
|
+
* level: ['C-Level'],
|
|
73
|
+
* companyHeadCount: ['1001-5000', '5001-10000'],
|
|
74
|
+
* limit: 25
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* // Fetch specific contacts (COSTS CREDITS)
|
|
78
|
+
* const enriched = await client.fetch(['contact-id-1', 'contact-id-2']);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createSmartProspectClient(config: SmartProspectConfig): SmartProspectClient | null;
|