linkedin-secret-sauce 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +512 -232
- package/dist/enrichment/auth/smartlead-auth.d.ts +3 -3
- package/dist/enrichment/auth/smartlead-auth.js +25 -25
- package/dist/enrichment/index.d.ts +6 -4
- package/dist/enrichment/index.js +25 -24
- package/dist/enrichment/matching.d.ts +8 -3
- package/dist/enrichment/matching.js +7 -5
- package/dist/enrichment/orchestrator.js +44 -14
- package/dist/enrichment/providers/construct.js +72 -14
- package/dist/enrichment/providers/hunter.js +6 -60
- package/dist/enrichment/providers/index.d.ts +0 -1
- package/dist/enrichment/providers/index.js +1 -3
- package/dist/enrichment/providers/ldd.js +5 -47
- package/dist/enrichment/providers/smartprospect.js +9 -14
- package/dist/enrichment/types.d.ts +23 -24
- package/dist/enrichment/types.js +22 -21
- package/dist/enrichment/utils/http-retry.d.ts +96 -0
- package/dist/enrichment/utils/http-retry.js +162 -0
- package/dist/enrichment/verification/index.d.ts +1 -1
- package/dist/enrichment/verification/index.js +3 -1
- package/dist/enrichment/verification/mx.d.ts +33 -0
- package/dist/enrichment/verification/mx.js +367 -7
- package/dist/index.d.ts +196 -6
- package/dist/index.js +159 -12
- package/dist/parsers/search-parser.js +7 -3
- package/package.json +30 -22
|
@@ -3,12 +3,52 @@
|
|
|
3
3
|
* MX Record Resolution and Email Verification
|
|
4
4
|
*
|
|
5
5
|
* Provides MX record resolution with timeout support and confidence scoring.
|
|
6
|
+
* Includes SMTP-based catch-all detection using RCPT TO verification.
|
|
6
7
|
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
7
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.verifyEmailsExist = verifyEmailsExist;
|
|
43
|
+
exports.checkDomainCatchAll = checkDomainCatchAll;
|
|
8
44
|
exports.verifyEmailMx = verifyEmailMx;
|
|
9
45
|
const promises_1 = require("node:dns/promises");
|
|
46
|
+
const net = __importStar(require("node:net"));
|
|
10
47
|
const disposable_domains_1 = require("../utils/disposable-domains");
|
|
11
48
|
const validation_1 = require("../utils/validation");
|
|
49
|
+
// Cache for catch-all results (domain -> isCatchAll)
|
|
50
|
+
const catchAllCache = new Map();
|
|
51
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
12
52
|
/**
|
|
13
53
|
* Resolve MX records with timeout
|
|
14
54
|
*
|
|
@@ -30,6 +70,273 @@ async function resolveMxWithTimeout(domain, timeoutMs) {
|
|
|
30
70
|
clearTimeout(timeoutId);
|
|
31
71
|
}
|
|
32
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate a random string for catch-all testing
|
|
75
|
+
*/
|
|
76
|
+
function generateRandomLocalPart() {
|
|
77
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
78
|
+
let result = 'catchalltest_';
|
|
79
|
+
for (let i = 0; i < 16; i++) {
|
|
80
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Detect if a domain is a catch-all by testing with a random email address
|
|
86
|
+
* Uses SMTP RCPT TO verification
|
|
87
|
+
*
|
|
88
|
+
* @param domain - Domain to test
|
|
89
|
+
* @param mxHost - MX server hostname
|
|
90
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
91
|
+
* @returns true if catch-all, false if not, null if unable to determine
|
|
92
|
+
*/
|
|
93
|
+
async function detectCatchAll(domain, mxHost, timeoutMs = 10000) {
|
|
94
|
+
// Check cache first
|
|
95
|
+
const cached = catchAllCache.get(domain);
|
|
96
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
97
|
+
return cached.isCatchAll;
|
|
98
|
+
}
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
const randomEmail = `${generateRandomLocalPart()}@${domain}`;
|
|
101
|
+
const socket = new net.Socket();
|
|
102
|
+
let step = 0;
|
|
103
|
+
let responseBuffer = '';
|
|
104
|
+
const cleanup = () => {
|
|
105
|
+
socket.removeAllListeners();
|
|
106
|
+
socket.destroy();
|
|
107
|
+
};
|
|
108
|
+
const timeout = setTimeout(() => {
|
|
109
|
+
cleanup();
|
|
110
|
+
resolve(null); // Unable to determine
|
|
111
|
+
}, timeoutMs);
|
|
112
|
+
socket.on('error', () => {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
cleanup();
|
|
115
|
+
resolve(null); // Unable to determine
|
|
116
|
+
});
|
|
117
|
+
socket.on('close', () => {
|
|
118
|
+
clearTimeout(timeout);
|
|
119
|
+
cleanup();
|
|
120
|
+
});
|
|
121
|
+
socket.on('data', (data) => {
|
|
122
|
+
responseBuffer += data.toString();
|
|
123
|
+
// Process complete lines
|
|
124
|
+
const lines = responseBuffer.split('\r\n');
|
|
125
|
+
responseBuffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
if (!line)
|
|
128
|
+
continue;
|
|
129
|
+
const code = parseInt(line.substring(0, 3), 10);
|
|
130
|
+
switch (step) {
|
|
131
|
+
case 0: // Waiting for greeting
|
|
132
|
+
if (code === 220) {
|
|
133
|
+
step = 1;
|
|
134
|
+
socket.write(`HELO verify.local\r\n`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
cleanup();
|
|
138
|
+
resolve(null);
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case 1: // Waiting for HELO response
|
|
142
|
+
if (code === 250) {
|
|
143
|
+
step = 2;
|
|
144
|
+
socket.write(`MAIL FROM:<test@verify.local>\r\n`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
cleanup();
|
|
148
|
+
resolve(null);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
case 2: // Waiting for MAIL FROM response
|
|
152
|
+
if (code === 250) {
|
|
153
|
+
step = 3;
|
|
154
|
+
socket.write(`RCPT TO:<${randomEmail}>\r\n`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
cleanup();
|
|
158
|
+
resolve(null);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
case 3: // Waiting for RCPT TO response - this is the key!
|
|
162
|
+
clearTimeout(timeout);
|
|
163
|
+
socket.write('QUIT\r\n');
|
|
164
|
+
// 250 = accepted (catch-all)
|
|
165
|
+
// 550, 551, 552, 553 = rejected (not catch-all)
|
|
166
|
+
// Other codes = unable to determine
|
|
167
|
+
const isCatchAll = code === 250;
|
|
168
|
+
const isRejected = code >= 550 && code <= 559;
|
|
169
|
+
if (isCatchAll || isRejected) {
|
|
170
|
+
// Cache the result
|
|
171
|
+
catchAllCache.set(domain, {
|
|
172
|
+
isCatchAll,
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
});
|
|
175
|
+
cleanup();
|
|
176
|
+
resolve(isCatchAll);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
cleanup();
|
|
180
|
+
resolve(null);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Connect to MX server on port 25
|
|
187
|
+
socket.connect(25, mxHost);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Verify if a specific email address exists via SMTP RCPT TO
|
|
192
|
+
*
|
|
193
|
+
* @param email - Email address to verify
|
|
194
|
+
* @param mxHost - MX server hostname
|
|
195
|
+
* @param timeoutMs - Timeout in milliseconds
|
|
196
|
+
* @returns true if exists, false if not, null if unable to determine
|
|
197
|
+
*/
|
|
198
|
+
async function verifyEmailExists(email, mxHost, timeoutMs = 10000) {
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
const socket = new net.Socket();
|
|
201
|
+
let step = 0;
|
|
202
|
+
let responseBuffer = '';
|
|
203
|
+
const cleanup = () => {
|
|
204
|
+
socket.removeAllListeners();
|
|
205
|
+
socket.destroy();
|
|
206
|
+
};
|
|
207
|
+
const timeout = setTimeout(() => {
|
|
208
|
+
cleanup();
|
|
209
|
+
resolve(null);
|
|
210
|
+
}, timeoutMs);
|
|
211
|
+
socket.on('error', () => {
|
|
212
|
+
clearTimeout(timeout);
|
|
213
|
+
cleanup();
|
|
214
|
+
resolve(null);
|
|
215
|
+
});
|
|
216
|
+
socket.on('close', () => {
|
|
217
|
+
clearTimeout(timeout);
|
|
218
|
+
cleanup();
|
|
219
|
+
});
|
|
220
|
+
socket.on('data', (data) => {
|
|
221
|
+
responseBuffer += data.toString();
|
|
222
|
+
const lines = responseBuffer.split('\r\n');
|
|
223
|
+
responseBuffer = lines.pop() || '';
|
|
224
|
+
for (const line of lines) {
|
|
225
|
+
if (!line)
|
|
226
|
+
continue;
|
|
227
|
+
const code = parseInt(line.substring(0, 3), 10);
|
|
228
|
+
switch (step) {
|
|
229
|
+
case 0: // Waiting for greeting
|
|
230
|
+
if (code === 220) {
|
|
231
|
+
step = 1;
|
|
232
|
+
socket.write(`HELO verify.local\r\n`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
cleanup();
|
|
236
|
+
resolve(null);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
case 1: // Waiting for HELO response
|
|
240
|
+
if (code === 250) {
|
|
241
|
+
step = 2;
|
|
242
|
+
socket.write(`MAIL FROM:<test@verify.local>\r\n`);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
cleanup();
|
|
246
|
+
resolve(null);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case 2: // Waiting for MAIL FROM response
|
|
250
|
+
if (code === 250) {
|
|
251
|
+
step = 3;
|
|
252
|
+
socket.write(`RCPT TO:<${email}>\r\n`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
cleanup();
|
|
256
|
+
resolve(null);
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
case 3: // Waiting for RCPT TO response
|
|
260
|
+
clearTimeout(timeout);
|
|
261
|
+
socket.write('QUIT\r\n');
|
|
262
|
+
// 250 = accepted (email exists or catch-all)
|
|
263
|
+
// 550, 551, 552, 553 = rejected (email doesn't exist)
|
|
264
|
+
const exists = code === 250;
|
|
265
|
+
const rejected = code >= 550 && code <= 559;
|
|
266
|
+
if (exists || rejected) {
|
|
267
|
+
cleanup();
|
|
268
|
+
resolve(exists);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
cleanup();
|
|
272
|
+
resolve(null);
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
socket.connect(25, mxHost);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Verify multiple email addresses with rate limiting
|
|
283
|
+
*
|
|
284
|
+
* @param emails - Array of email addresses to verify
|
|
285
|
+
* @param options - Options including delay between checks
|
|
286
|
+
* @returns Array of verification results
|
|
287
|
+
*/
|
|
288
|
+
async function verifyEmailsExist(emails, options) {
|
|
289
|
+
const delayMs = options?.delayMs ?? 3000; // 3 second delay between checks
|
|
290
|
+
const timeoutMs = options?.timeoutMs ?? 10000;
|
|
291
|
+
const results = [];
|
|
292
|
+
// Group emails by domain to reuse MX lookups
|
|
293
|
+
const emailsByDomain = new Map();
|
|
294
|
+
for (const email of emails) {
|
|
295
|
+
const domain = email.split('@')[1]?.toLowerCase();
|
|
296
|
+
if (domain) {
|
|
297
|
+
const existing = emailsByDomain.get(domain) || [];
|
|
298
|
+
existing.push(email);
|
|
299
|
+
emailsByDomain.set(domain, existing);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
results.push({ email, exists: null, mxHost: null, error: 'Invalid email format' });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Process each domain
|
|
306
|
+
for (const [domain, domainEmails] of emailsByDomain) {
|
|
307
|
+
// Get MX records for domain
|
|
308
|
+
const mx = await resolveMxWithTimeout(domain, 5000);
|
|
309
|
+
if (!mx || mx.length === 0) {
|
|
310
|
+
for (const email of domainEmails) {
|
|
311
|
+
results.push({ email, exists: null, mxHost: null, error: 'No MX records found' });
|
|
312
|
+
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const sortedMx = mx.sort((a, b) => a.priority - b.priority);
|
|
316
|
+
const mxHost = sortedMx[0].exchange;
|
|
317
|
+
// Verify each email with delay
|
|
318
|
+
for (let i = 0; i < domainEmails.length; i++) {
|
|
319
|
+
const email = domainEmails[i];
|
|
320
|
+
// Add delay between checks (except for first one)
|
|
321
|
+
if (i > 0) {
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const exists = await verifyEmailExists(email, mxHost, timeoutMs);
|
|
326
|
+
results.push({ email, exists, mxHost });
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
results.push({
|
|
330
|
+
email,
|
|
331
|
+
exists: null,
|
|
332
|
+
mxHost,
|
|
333
|
+
error: err instanceof Error ? err.message : 'Verification failed',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return results;
|
|
339
|
+
}
|
|
33
340
|
/**
|
|
34
341
|
* Calculate confidence score based on verification factors
|
|
35
342
|
*/
|
|
@@ -43,9 +350,14 @@ function calculateConfidence(params) {
|
|
|
43
350
|
score += 20;
|
|
44
351
|
else if (mxCount === 1)
|
|
45
352
|
score += 10;
|
|
46
|
-
//
|
|
47
|
-
|
|
353
|
+
// Catch-all scoring:
|
|
354
|
+
// - Not catch-all (false): +30 bonus
|
|
355
|
+
// - Is catch-all (true): no bonus
|
|
356
|
+
// - Unknown (null): +15 (partial bonus)
|
|
357
|
+
if (isCatchAll === false)
|
|
48
358
|
score += 30;
|
|
359
|
+
else if (isCatchAll === null)
|
|
360
|
+
score += 15;
|
|
49
361
|
// Non-role account bonus
|
|
50
362
|
if (!isRoleAccount)
|
|
51
363
|
score += 20;
|
|
@@ -61,8 +373,50 @@ function calculateConfidence(params) {
|
|
|
61
373
|
* @param options - Verification options
|
|
62
374
|
* @returns Verification result with confidence score
|
|
63
375
|
*/
|
|
376
|
+
/**
|
|
377
|
+
* Check if a domain is a catch-all (accepts all email addresses)
|
|
378
|
+
*
|
|
379
|
+
* @param domain - Domain to check
|
|
380
|
+
* @param options - Options including timeout
|
|
381
|
+
* @returns Object with isCatchAll status and MX records
|
|
382
|
+
*/
|
|
383
|
+
async function checkDomainCatchAll(domain, options) {
|
|
384
|
+
const timeoutMs = options?.timeoutMs ?? 10000;
|
|
385
|
+
try {
|
|
386
|
+
// Get MX records
|
|
387
|
+
const mx = await resolveMxWithTimeout(domain, 5000);
|
|
388
|
+
if (!mx || mx.length === 0) {
|
|
389
|
+
return {
|
|
390
|
+
isCatchAll: null,
|
|
391
|
+
mxRecords: [],
|
|
392
|
+
mxHost: null,
|
|
393
|
+
error: 'No MX records found',
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
// Sort by priority and get primary
|
|
397
|
+
const sortedMx = mx.sort((a, b) => a.priority - b.priority);
|
|
398
|
+
const primaryMxHost = sortedMx[0].exchange;
|
|
399
|
+
const mxRecords = sortedMx.map((m) => m.exchange);
|
|
400
|
+
// Detect catch-all
|
|
401
|
+
const isCatchAll = await detectCatchAll(domain, primaryMxHost, timeoutMs);
|
|
402
|
+
return {
|
|
403
|
+
isCatchAll,
|
|
404
|
+
mxRecords,
|
|
405
|
+
mxHost: primaryMxHost,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
return {
|
|
410
|
+
isCatchAll: null,
|
|
411
|
+
mxRecords: [],
|
|
412
|
+
mxHost: null,
|
|
413
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
64
417
|
async function verifyEmailMx(email, options) {
|
|
65
418
|
const timeoutMs = options?.timeoutMs ?? 5000;
|
|
419
|
+
const checkCatchAll = options?.checkCatchAll ?? false;
|
|
66
420
|
// Step 1: Syntax validation
|
|
67
421
|
if (!(0, validation_1.isValidEmailSyntax)(email)) {
|
|
68
422
|
return {
|
|
@@ -113,8 +467,14 @@ async function verifyEmailMx(email, options) {
|
|
|
113
467
|
mxRecords: [],
|
|
114
468
|
};
|
|
115
469
|
}
|
|
116
|
-
//
|
|
117
|
-
const
|
|
470
|
+
// Sort MX records by priority (lowest first)
|
|
471
|
+
const sortedMx = mx.sort((a, b) => a.priority - b.priority);
|
|
472
|
+
const primaryMxHost = sortedMx[0].exchange;
|
|
473
|
+
// Detect catch-all if requested (uses SMTP RCPT TO verification)
|
|
474
|
+
let catchAll = null;
|
|
475
|
+
if (checkCatchAll) {
|
|
476
|
+
catchAll = await detectCatchAll(domain, primaryMxHost, timeoutMs);
|
|
477
|
+
}
|
|
118
478
|
// Calculate confidence
|
|
119
479
|
const confidence = calculateConfidence({
|
|
120
480
|
mxValid: true,
|
|
@@ -127,17 +487,17 @@ async function verifyEmailMx(email, options) {
|
|
|
127
487
|
// Determine reason
|
|
128
488
|
let reason = 'valid';
|
|
129
489
|
if (!valid) {
|
|
130
|
-
if (catchAll)
|
|
490
|
+
if (catchAll === true)
|
|
131
491
|
reason = 'catch_all';
|
|
132
492
|
else if (roleAccount)
|
|
133
493
|
reason = 'role_account';
|
|
134
494
|
}
|
|
135
|
-
const mxRecords =
|
|
495
|
+
const mxRecords = sortedMx.map((m) => m.exchange);
|
|
136
496
|
return {
|
|
137
497
|
valid,
|
|
138
498
|
confidence,
|
|
139
499
|
reason,
|
|
140
|
-
isCatchAll: catchAll,
|
|
500
|
+
isCatchAll: catchAll ?? false, // Default to false if unknown
|
|
141
501
|
isRoleAccount: roleAccount,
|
|
142
502
|
isDisposable: false,
|
|
143
503
|
mxRecords,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkedIn Secret Sauce
|
|
3
|
+
*
|
|
4
|
+
* A complete LinkedIn Sales Navigator client + Email Enrichment library.
|
|
5
|
+
*
|
|
6
|
+
* ## Two Main Modules
|
|
7
|
+
*
|
|
8
|
+
* ### 1. LinkedIn API
|
|
9
|
+
* Server-side LinkedIn Sales Navigator client with automatic cookie management,
|
|
10
|
+
* retries, caching, and parsing.
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { initializeLinkedInClient, searchSalesLeads, getProfileByVanity } from 'linkedin-secret-sauce';
|
|
14
|
+
*
|
|
15
|
+
* initializeLinkedInClient({
|
|
16
|
+
* cosiallApiUrl: process.env.COSIALL_API_URL,
|
|
17
|
+
* cosiallApiKey: process.env.COSIALL_API_KEY,
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const profile = await getProfileByVanity('john-doe');
|
|
21
|
+
* const leads = await searchSalesLeads('cto fintech', { count: 25 });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ### 2. Email Enrichment
|
|
25
|
+
* Find business emails using multiple providers with waterfall/parallel strategies.
|
|
26
|
+
*
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { createEnrichmentClient } from 'linkedin-secret-sauce';
|
|
29
|
+
*
|
|
30
|
+
* const enrichment = createEnrichmentClient({
|
|
31
|
+
* providers: {
|
|
32
|
+
* hunter: { apiKey: process.env.HUNTER_API_KEY },
|
|
33
|
+
* bouncer: { apiKey: process.env.BOUNCER_API_KEY },
|
|
34
|
+
* },
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* const result = await enrichment.enrich({
|
|
38
|
+
* firstName: 'John',
|
|
39
|
+
* lastName: 'Doe',
|
|
40
|
+
* domain: 'acme.com',
|
|
41
|
+
* });
|
|
42
|
+
* console.log(result.business_email); // john.doe@acme.com
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @packageDocumentation
|
|
46
|
+
*/
|
|
1
47
|
export { initializeLinkedInClient, getConfig } from "./config";
|
|
2
48
|
export type { LinkedInClientConfig } from "./config";
|
|
49
|
+
export * from "./linkedin-api";
|
|
50
|
+
export * from "./types";
|
|
51
|
+
export type { LinkedInTenure, LinkedInPosition, LinkedInSpotlightBadge, SalesLeadSearchResult, } from "./types";
|
|
3
52
|
export { LinkedInClientError } from "./utils/errors";
|
|
4
53
|
export * from "./cosiall-client";
|
|
5
54
|
export * from "./cookie-pool";
|
|
6
55
|
export { parseFullProfile } from "./parsers/profile-parser";
|
|
7
56
|
export { parseSalesSearchResults } from "./parsers/search-parser";
|
|
8
|
-
export * from "./linkedin-api";
|
|
9
|
-
export * from "./types";
|
|
10
|
-
export type { LinkedInTenure, LinkedInPosition, LinkedInSpotlightBadge, SalesLeadSearchResult, } from "./types";
|
|
11
57
|
export * from "./utils/metrics";
|
|
12
58
|
export { getRequestHistory, clearRequestHistory, } from "./utils/request-history";
|
|
13
59
|
export type { RequestHistoryEntry } from "./utils/request-history";
|
|
14
60
|
export * from "./constants";
|
|
61
|
+
/**
|
|
62
|
+
* Create an enrichment client for finding business emails
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const enrichment = createEnrichmentClient({
|
|
67
|
+
* providers: {
|
|
68
|
+
* construct: {}, // FREE pattern matching
|
|
69
|
+
* hunter: { apiKey: 'xxx' },
|
|
70
|
+
* bouncer: { apiKey: 'xxx' },
|
|
71
|
+
* },
|
|
72
|
+
* onCost: (provider, cost) => console.log(`${provider}: $${cost}`),
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* const result = await enrichment.enrich({
|
|
76
|
+
* firstName: 'John',
|
|
77
|
+
* lastName: 'Doe',
|
|
78
|
+
* domain: 'acme.com',
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
15
82
|
export { createEnrichmentClient } from "./enrichment";
|
|
16
|
-
export type {
|
|
17
|
-
|
|
18
|
-
|
|
83
|
+
export type {
|
|
84
|
+
/** Result returned by enrichment operations */
|
|
85
|
+
CanonicalEmail,
|
|
86
|
+
/** Input candidate for email lookup */
|
|
87
|
+
EnrichmentCandidate,
|
|
88
|
+
/** Raw provider result */
|
|
89
|
+
ProviderResult,
|
|
90
|
+
/** Provider function signature */
|
|
91
|
+
ProviderFunc,
|
|
92
|
+
/** Supported provider names */
|
|
93
|
+
ProviderName,
|
|
94
|
+
/** Full client configuration */
|
|
95
|
+
EnrichmentClientConfig,
|
|
96
|
+
/** Enrichment client interface */
|
|
97
|
+
EnrichmentClient,
|
|
98
|
+
/** Provider configuration map */
|
|
99
|
+
ProvidersConfig,
|
|
100
|
+
/** Enrichment options */
|
|
101
|
+
EnrichmentOptions,
|
|
102
|
+
/** Batch processing options */
|
|
103
|
+
BatchEnrichmentOptions,
|
|
104
|
+
/** Hunter.io configuration */
|
|
105
|
+
HunterConfig,
|
|
106
|
+
/** SmartProspect/SmartLead configuration */
|
|
107
|
+
SmartProspectConfig,
|
|
108
|
+
/** LinkedIn Data Dump configuration */
|
|
109
|
+
LddConfig,
|
|
110
|
+
/** Dropcontact configuration */
|
|
111
|
+
DropcontactConfig,
|
|
112
|
+
/** Construct provider configuration */
|
|
113
|
+
ConstructConfig,
|
|
114
|
+
/** Cache adapter interface */
|
|
115
|
+
CacheAdapter,
|
|
116
|
+
/** Cost tracking callback */
|
|
117
|
+
CostCallback,
|
|
118
|
+
/** Logger interface */
|
|
119
|
+
EnrichmentLogger,
|
|
120
|
+
/** Email verification result */
|
|
121
|
+
VerificationResult,
|
|
122
|
+
/** SmartProspect contact */
|
|
123
|
+
SmartProspectContact,
|
|
124
|
+
/** SmartProspect search filters */
|
|
125
|
+
SmartProspectSearchFilters,
|
|
126
|
+
/** LDD profile data */
|
|
127
|
+
LddProfileData,
|
|
128
|
+
/** LDD API response */
|
|
129
|
+
LddApiResponse, } from "./enrichment";
|
|
130
|
+
export {
|
|
131
|
+
/**
|
|
132
|
+
* Check if email is from a personal domain (Gmail, Yahoo, etc.)
|
|
133
|
+
* @example isPersonalEmail('john@gmail.com') // true
|
|
134
|
+
*/
|
|
135
|
+
isPersonalEmail,
|
|
136
|
+
/**
|
|
137
|
+
* Check if email is from a business domain
|
|
138
|
+
* @example isBusinessEmail('john@acme.com') // true
|
|
139
|
+
*/
|
|
140
|
+
isBusinessEmail,
|
|
141
|
+
/**
|
|
142
|
+
* Check if domain is a personal email provider
|
|
143
|
+
* @example isPersonalDomain('gmail.com') // true
|
|
144
|
+
*/
|
|
145
|
+
isPersonalDomain,
|
|
146
|
+
/**
|
|
147
|
+
* Check if email is from a disposable/temporary email service
|
|
148
|
+
* @example isDisposableEmail('test@mailinator.com') // true
|
|
149
|
+
*/
|
|
150
|
+
isDisposableEmail,
|
|
151
|
+
/**
|
|
152
|
+
* Check if domain is a disposable email provider
|
|
153
|
+
* @example isDisposableDomain('guerrillamail.com') // true
|
|
154
|
+
*/
|
|
155
|
+
isDisposableDomain,
|
|
156
|
+
/**
|
|
157
|
+
* Validate email syntax
|
|
158
|
+
* @example isValidEmailSyntax('john@example.com') // true
|
|
159
|
+
*/
|
|
160
|
+
isValidEmailSyntax,
|
|
161
|
+
/**
|
|
162
|
+
* Check if email is a role account (info@, support@, etc.)
|
|
163
|
+
* @example isRoleAccount('info@company.com') // true
|
|
164
|
+
*/
|
|
165
|
+
isRoleAccount,
|
|
166
|
+
/**
|
|
167
|
+
* Verify email via MX record lookup
|
|
168
|
+
*/
|
|
169
|
+
verifyEmailMx,
|
|
170
|
+
/** Set of known personal email domains */
|
|
171
|
+
PERSONAL_DOMAINS,
|
|
172
|
+
/** Set of known disposable email domains */
|
|
173
|
+
DISPOSABLE_DOMAINS,
|
|
174
|
+
/** Default provider execution order */
|
|
175
|
+
DEFAULT_PROVIDER_ORDER,
|
|
176
|
+
/** Cost per lookup by provider (USD) */
|
|
177
|
+
PROVIDER_COSTS, } from "./enrichment";
|
|
178
|
+
export {
|
|
179
|
+
/**
|
|
180
|
+
* Get JWT token for SmartLead API (cached, auto-refreshes)
|
|
181
|
+
* @example const token = await getSmartLeadToken({ credentials: { email, password } });
|
|
182
|
+
*/
|
|
183
|
+
getSmartLeadToken,
|
|
184
|
+
/**
|
|
185
|
+
* Get SmartLead user info from cached login
|
|
186
|
+
*/
|
|
187
|
+
getSmartLeadUser,
|
|
188
|
+
/**
|
|
189
|
+
* Clear cached token for an email (e.g., on 401 error)
|
|
190
|
+
*/
|
|
191
|
+
clearSmartLeadToken,
|
|
192
|
+
/**
|
|
193
|
+
* Clear all cached SmartLead tokens
|
|
194
|
+
*/
|
|
195
|
+
clearAllSmartLeadTokens,
|
|
196
|
+
/**
|
|
197
|
+
* Get token cache statistics for debugging
|
|
198
|
+
*/
|
|
199
|
+
getSmartLeadTokenCacheStats, } from "./enrichment";
|
|
200
|
+
export type {
|
|
201
|
+
/** SmartLead login credentials */
|
|
202
|
+
SmartLeadCredentials,
|
|
203
|
+
/** SmartLead authentication configuration */
|
|
204
|
+
SmartLeadAuthConfig,
|
|
205
|
+
/** SmartLead user info */
|
|
206
|
+
SmartLeadUser,
|
|
207
|
+
/** SmartLead login response */
|
|
208
|
+
SmartLeadLoginResponse, } from "./enrichment";
|