linkedin-secret-sauce 0.7.2 → 0.8.0
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.
|
@@ -44,4 +44,4 @@ export { extractNumericLinkedInId } from "./providers/ldd";
|
|
|
44
44
|
export { createSmartProspectClient, type SmartProspectClient, type SmartProspectLocationOptions, } from "./providers/smartprospect";
|
|
45
45
|
export { enrichBusinessEmail, enrichBatch, enrichAllEmails, enrichAllBatch } from "./orchestrator";
|
|
46
46
|
export { getSmartLeadToken, getSmartLeadUser, clearSmartLeadToken, clearAllSmartLeadTokens, getSmartLeadTokenCacheStats, enableFileCache, disableFileCache, isFileCacheEnabled, clearFileCache, type SmartLeadCredentials, type SmartLeadAuthConfig, type SmartLeadUser, type SmartLeadLoginResponse, } from "./auth";
|
|
47
|
-
export { calculateMatchConfidence, classifyMatchQuality, findBestMatch, matchContacts, buildSmartProspectFiltersFromLinkedIn, parseLinkedInSearchResponse, enrichLinkedInContact, enrichLinkedInContactsBatch, createLinkedInEnricher, type LinkedInContact, type MatchResult, type MatchOptions, type LinkedInEnrichmentResult, type LinkedInEnrichmentOptions, } from "./matching";
|
|
47
|
+
export { calculateMatchConfidence, classifyMatchQuality, findBestMatch, matchContacts, buildSmartProspectFiltersFromLinkedIn, parseLinkedInSearchResponse, enrichLinkedInContact, enrichLinkedInContactsBatch, createLinkedInEnricher, getEmailsForLinkedInContact, getEmailsForLinkedInContactsBatch, type LinkedInContact, type MatchResult, type MatchOptions, type LinkedInEnrichmentResult, type LinkedInEnrichmentOptions, type EmailResult, type GetEmailsResult, type GetEmailsConfig, type GetEmailsOptions, } from "./matching";
|
package/dist/enrichment/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
42
42
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.createLinkedInEnricher = exports.enrichLinkedInContactsBatch = exports.enrichLinkedInContact = exports.parseLinkedInSearchResponse = exports.buildSmartProspectFiltersFromLinkedIn = exports.matchContacts = exports.findBestMatch = exports.classifyMatchQuality = exports.calculateMatchConfidence = exports.clearFileCache = exports.isFileCacheEnabled = exports.disableFileCache = exports.enableFileCache = exports.getSmartLeadTokenCacheStats = exports.clearAllSmartLeadTokens = exports.clearSmartLeadToken = exports.getSmartLeadUser = exports.getSmartLeadToken = exports.enrichAllBatch = exports.enrichAllEmails = exports.enrichBatch = exports.enrichBusinessEmail = exports.createSmartProspectClient = exports.extractNumericLinkedInId = exports.createDropcontactProvider = exports.createApolloProvider = exports.createHunterProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = exports.verifyEmailMx = exports.extractLinkedInUsername = exports.hostnameFromUrl = exports.cleanNamePart = exports.asciiFold = exports.isRoleAccount = exports.isValidEmailSyntax = exports.DISPOSABLE_DOMAINS = exports.isDisposableDomain = exports.isDisposableEmail = exports.PERSONAL_DOMAINS = exports.isPersonalDomain = exports.isBusinessEmail = exports.isPersonalEmail = void 0;
|
|
45
|
+
exports.getEmailsForLinkedInContactsBatch = exports.getEmailsForLinkedInContact = exports.createLinkedInEnricher = exports.enrichLinkedInContactsBatch = exports.enrichLinkedInContact = exports.parseLinkedInSearchResponse = exports.buildSmartProspectFiltersFromLinkedIn = exports.matchContacts = exports.findBestMatch = exports.classifyMatchQuality = exports.calculateMatchConfidence = exports.clearFileCache = exports.isFileCacheEnabled = exports.disableFileCache = exports.enableFileCache = exports.getSmartLeadTokenCacheStats = exports.clearAllSmartLeadTokens = exports.clearSmartLeadToken = exports.getSmartLeadUser = exports.getSmartLeadToken = exports.enrichAllBatch = exports.enrichAllEmails = exports.enrichBatch = exports.enrichBusinessEmail = exports.createSmartProspectClient = exports.extractNumericLinkedInId = exports.createDropcontactProvider = exports.createApolloProvider = exports.createHunterProvider = exports.createSmartProspectProvider = exports.createLddProvider = exports.createConstructProvider = exports.verifyEmailMx = exports.extractLinkedInUsername = exports.hostnameFromUrl = exports.cleanNamePart = exports.asciiFold = exports.isRoleAccount = exports.isValidEmailSyntax = exports.DISPOSABLE_DOMAINS = exports.isDisposableDomain = exports.isDisposableEmail = exports.PERSONAL_DOMAINS = exports.isPersonalDomain = exports.isBusinessEmail = exports.isPersonalEmail = void 0;
|
|
46
46
|
exports.createEnrichmentClient = createEnrichmentClient;
|
|
47
47
|
const orchestrator_1 = require("./orchestrator");
|
|
48
48
|
const construct_1 = require("./providers/construct");
|
|
@@ -287,3 +287,6 @@ Object.defineProperty(exports, "parseLinkedInSearchResponse", { enumerable: true
|
|
|
287
287
|
Object.defineProperty(exports, "enrichLinkedInContact", { enumerable: true, get: function () { return matching_1.enrichLinkedInContact; } });
|
|
288
288
|
Object.defineProperty(exports, "enrichLinkedInContactsBatch", { enumerable: true, get: function () { return matching_1.enrichLinkedInContactsBatch; } });
|
|
289
289
|
Object.defineProperty(exports, "createLinkedInEnricher", { enumerable: true, get: function () { return matching_1.createLinkedInEnricher; } });
|
|
290
|
+
// UNIFIED email lookup (recommended for most use cases)
|
|
291
|
+
Object.defineProperty(exports, "getEmailsForLinkedInContact", { enumerable: true, get: function () { return matching_1.getEmailsForLinkedInContact; } });
|
|
292
|
+
Object.defineProperty(exports, "getEmailsForLinkedInContactsBatch", { enumerable: true, get: function () { return matching_1.getEmailsForLinkedInContactsBatch; } });
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Matches contacts between LinkedIn Sales Navigator and SmartProspect
|
|
5
5
|
* to find the same person across both platforms.
|
|
6
6
|
*/
|
|
7
|
-
import type { SmartProspectContact, SmartProspectConfig, SmartProspectFetchResponse } from './types';
|
|
7
|
+
import type { SmartProspectContact, SmartProspectConfig, SmartProspectFetchResponse, LddConfig } from './types';
|
|
8
8
|
import { type SmartProspectClient } from './providers/smartprospect';
|
|
9
9
|
/**
|
|
10
10
|
* LinkedIn Sales Navigator contact (simplified from API response)
|
|
@@ -239,3 +239,112 @@ export declare function createLinkedInEnricher(smartProspectConfig: SmartProspec
|
|
|
239
239
|
/** Fetch email for a specific SmartProspect contact ID (COSTS CREDITS) */
|
|
240
240
|
fetchEmail: (contactId: string) => Promise<SmartProspectFetchResponse>;
|
|
241
241
|
} | null;
|
|
242
|
+
/**
|
|
243
|
+
* Email result from unified lookup
|
|
244
|
+
*/
|
|
245
|
+
export interface EmailResult {
|
|
246
|
+
/** The email address */
|
|
247
|
+
email: string;
|
|
248
|
+
/** Which provider found this email */
|
|
249
|
+
source: 'ldd' | 'smartprospect';
|
|
250
|
+
/** Confidence score (0-100) */
|
|
251
|
+
confidence: number;
|
|
252
|
+
/** Email type classification */
|
|
253
|
+
type: 'business' | 'personal' | 'unknown';
|
|
254
|
+
/** Whether the email was verified */
|
|
255
|
+
verified: boolean;
|
|
256
|
+
/** Email deliverability score (0-1) for SmartProspect emails */
|
|
257
|
+
deliverability?: number;
|
|
258
|
+
/** Additional metadata */
|
|
259
|
+
metadata?: Record<string, unknown>;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Result from unified email lookup
|
|
263
|
+
*/
|
|
264
|
+
export interface GetEmailsResult {
|
|
265
|
+
/** Whether lookup was successful (found at least one email) */
|
|
266
|
+
success: boolean;
|
|
267
|
+
/** All found emails, sorted by confidence (highest first) */
|
|
268
|
+
emails: EmailResult[];
|
|
269
|
+
/** Numeric LinkedIn ID extracted from objectUrn */
|
|
270
|
+
numericLinkedInId: string | null;
|
|
271
|
+
/** Which providers were queried */
|
|
272
|
+
providersQueried: ('ldd' | 'smartprospect')[];
|
|
273
|
+
/** Error message if failed */
|
|
274
|
+
error?: string;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Configuration for unified email lookup
|
|
278
|
+
*/
|
|
279
|
+
export interface GetEmailsConfig {
|
|
280
|
+
/** LDD configuration (optional but recommended - FREE) */
|
|
281
|
+
ldd?: LddConfig;
|
|
282
|
+
/** SmartProspect configuration (optional - PAID fallback) */
|
|
283
|
+
smartprospect?: SmartProspectConfig;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Options for unified email lookup
|
|
287
|
+
*/
|
|
288
|
+
export interface GetEmailsOptions {
|
|
289
|
+
/** Skip LDD lookup (default: false) */
|
|
290
|
+
skipLdd?: boolean;
|
|
291
|
+
/** Skip SmartProspect lookup (default: false) */
|
|
292
|
+
skipSmartProspect?: boolean;
|
|
293
|
+
/** Minimum match confidence for SmartProspect (default: 60) */
|
|
294
|
+
minMatchConfidence?: number;
|
|
295
|
+
/** Include company in SmartProspect search (default: true) */
|
|
296
|
+
includeCompany?: boolean;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get emails for a LinkedIn contact - UNIFIED FUNCTION
|
|
300
|
+
*
|
|
301
|
+
* This is the main function consumers should use. It:
|
|
302
|
+
* 1. Extracts the numeric LinkedIn ID from objectUrn (stable identifier)
|
|
303
|
+
* 2. Tries LDD first (FREE, uses numeric ID for reliable lookup)
|
|
304
|
+
* 3. Falls back to SmartProspect if LDD has no results (PAID)
|
|
305
|
+
* 4. Returns ALL emails with unified scoring
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* import { getEmailsForLinkedInContact } from 'linkedin-secret-sauce';
|
|
310
|
+
*
|
|
311
|
+
* // From your LinkedIn Sales Nav search result
|
|
312
|
+
* const contact = {
|
|
313
|
+
* objectUrn: 'urn:li:member:307567', // IMPORTANT: Include this!
|
|
314
|
+
* firstName: 'Jim',
|
|
315
|
+
* lastName: 'DeMaio',
|
|
316
|
+
* currentPositions: [{ companyName: 'Acme Corp', title: 'CEO' }]
|
|
317
|
+
* };
|
|
318
|
+
*
|
|
319
|
+
* const result = await getEmailsForLinkedInContact(contact, {
|
|
320
|
+
* ldd: {
|
|
321
|
+
* apiUrl: process.env.LDD_API_URL,
|
|
322
|
+
* apiToken: process.env.LDD_API_TOKEN,
|
|
323
|
+
* },
|
|
324
|
+
* smartprospect: {
|
|
325
|
+
* email: process.env.SMARTLEAD_EMAIL,
|
|
326
|
+
* password: process.env.SMARTLEAD_PASSWORD,
|
|
327
|
+
* },
|
|
328
|
+
* });
|
|
329
|
+
*
|
|
330
|
+
* if (result.success) {
|
|
331
|
+
* console.log('Found emails:', result.emails);
|
|
332
|
+
* // [
|
|
333
|
+
* // { email: 'jim@acme.com', source: 'ldd', confidence: 95, type: 'business' },
|
|
334
|
+
* // { email: 'jim@gmail.com', source: 'ldd', confidence: 70, type: 'personal' }
|
|
335
|
+
* // ]
|
|
336
|
+
* }
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
export declare function getEmailsForLinkedInContact(contact: LinkedInContact, config: GetEmailsConfig, options?: GetEmailsOptions): Promise<GetEmailsResult>;
|
|
340
|
+
/**
|
|
341
|
+
* Get emails for multiple LinkedIn contacts in batch
|
|
342
|
+
*
|
|
343
|
+
* Processes contacts sequentially to avoid rate limits.
|
|
344
|
+
*/
|
|
345
|
+
export declare function getEmailsForLinkedInContactsBatch(contacts: LinkedInContact[], config: GetEmailsConfig, options?: GetEmailsOptions & {
|
|
346
|
+
/** Delay between requests in ms (default: 200) */
|
|
347
|
+
delayMs?: number;
|
|
348
|
+
/** Callback for progress updates */
|
|
349
|
+
onProgress?: (completed: number, total: number, result: GetEmailsResult) => void;
|
|
350
|
+
}): Promise<GetEmailsResult[]>;
|
|
@@ -15,6 +15,8 @@ exports.parseLinkedInSearchResponse = parseLinkedInSearchResponse;
|
|
|
15
15
|
exports.enrichLinkedInContact = enrichLinkedInContact;
|
|
16
16
|
exports.enrichLinkedInContactsBatch = enrichLinkedInContactsBatch;
|
|
17
17
|
exports.createLinkedInEnricher = createLinkedInEnricher;
|
|
18
|
+
exports.getEmailsForLinkedInContact = getEmailsForLinkedInContact;
|
|
19
|
+
exports.getEmailsForLinkedInContactsBatch = getEmailsForLinkedInContactsBatch;
|
|
18
20
|
const smartprospect_1 = require("./providers/smartprospect");
|
|
19
21
|
const ldd_1 = require("./providers/ldd");
|
|
20
22
|
// =============================================================================
|
|
@@ -624,3 +626,196 @@ function createLinkedInEnricher(smartProspectConfig, defaultOptions = {}) {
|
|
|
624
626
|
},
|
|
625
627
|
};
|
|
626
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Get emails for a LinkedIn contact - UNIFIED FUNCTION
|
|
631
|
+
*
|
|
632
|
+
* This is the main function consumers should use. It:
|
|
633
|
+
* 1. Extracts the numeric LinkedIn ID from objectUrn (stable identifier)
|
|
634
|
+
* 2. Tries LDD first (FREE, uses numeric ID for reliable lookup)
|
|
635
|
+
* 3. Falls back to SmartProspect if LDD has no results (PAID)
|
|
636
|
+
* 4. Returns ALL emails with unified scoring
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* ```ts
|
|
640
|
+
* import { getEmailsForLinkedInContact } from 'linkedin-secret-sauce';
|
|
641
|
+
*
|
|
642
|
+
* // From your LinkedIn Sales Nav search result
|
|
643
|
+
* const contact = {
|
|
644
|
+
* objectUrn: 'urn:li:member:307567', // IMPORTANT: Include this!
|
|
645
|
+
* firstName: 'Jim',
|
|
646
|
+
* lastName: 'DeMaio',
|
|
647
|
+
* currentPositions: [{ companyName: 'Acme Corp', title: 'CEO' }]
|
|
648
|
+
* };
|
|
649
|
+
*
|
|
650
|
+
* const result = await getEmailsForLinkedInContact(contact, {
|
|
651
|
+
* ldd: {
|
|
652
|
+
* apiUrl: process.env.LDD_API_URL,
|
|
653
|
+
* apiToken: process.env.LDD_API_TOKEN,
|
|
654
|
+
* },
|
|
655
|
+
* smartprospect: {
|
|
656
|
+
* email: process.env.SMARTLEAD_EMAIL,
|
|
657
|
+
* password: process.env.SMARTLEAD_PASSWORD,
|
|
658
|
+
* },
|
|
659
|
+
* });
|
|
660
|
+
*
|
|
661
|
+
* if (result.success) {
|
|
662
|
+
* console.log('Found emails:', result.emails);
|
|
663
|
+
* // [
|
|
664
|
+
* // { email: 'jim@acme.com', source: 'ldd', confidence: 95, type: 'business' },
|
|
665
|
+
* // { email: 'jim@gmail.com', source: 'ldd', confidence: 70, type: 'personal' }
|
|
666
|
+
* // ]
|
|
667
|
+
* }
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
async function getEmailsForLinkedInContact(contact, config, options = {}) {
|
|
671
|
+
const { skipLdd = false, skipSmartProspect = false, minMatchConfidence = 60, includeCompany = true, } = options;
|
|
672
|
+
// Extract numeric ID from objectUrn
|
|
673
|
+
const numericLinkedInId = (0, ldd_1.extractNumericLinkedInId)(contact.objectUrn);
|
|
674
|
+
const result = {
|
|
675
|
+
success: false,
|
|
676
|
+
emails: [],
|
|
677
|
+
numericLinkedInId,
|
|
678
|
+
providersQueried: [],
|
|
679
|
+
};
|
|
680
|
+
// ==========================================================================
|
|
681
|
+
// Step 1: Try LDD first (FREE)
|
|
682
|
+
// ==========================================================================
|
|
683
|
+
if (!skipLdd && config.ldd?.apiUrl && config.ldd?.apiToken) {
|
|
684
|
+
result.providersQueried.push('ldd');
|
|
685
|
+
try {
|
|
686
|
+
const lddProvider = (0, ldd_1.createLddProvider)(config.ldd);
|
|
687
|
+
const lddResult = await lddProvider({
|
|
688
|
+
numericLinkedInId: numericLinkedInId || undefined,
|
|
689
|
+
linkedinUsername: undefined, // Let LDD provider extract from contact if needed
|
|
690
|
+
firstName: contact.firstName,
|
|
691
|
+
lastName: contact.lastName,
|
|
692
|
+
});
|
|
693
|
+
if (lddResult && 'emails' in lddResult && lddResult.emails.length > 0) {
|
|
694
|
+
// LDD found emails!
|
|
695
|
+
for (const emailData of lddResult.emails) {
|
|
696
|
+
result.emails.push({
|
|
697
|
+
email: emailData.email,
|
|
698
|
+
source: 'ldd',
|
|
699
|
+
confidence: emailData.confidence ?? 90,
|
|
700
|
+
type: emailData.metadata?.emailTypeClassified || 'unknown',
|
|
701
|
+
verified: emailData.verified ?? true,
|
|
702
|
+
metadata: emailData.metadata,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
result.success = true;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (err) {
|
|
709
|
+
// LDD failed, continue to SmartProspect
|
|
710
|
+
result.error = `LDD error: ${err instanceof Error ? err.message : 'Unknown error'}`;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
// ==========================================================================
|
|
714
|
+
// Step 2: Fall back to SmartProspect if LDD didn't find emails (PAID)
|
|
715
|
+
// ==========================================================================
|
|
716
|
+
if (!result.success && !skipSmartProspect && config.smartprospect) {
|
|
717
|
+
result.providersQueried.push('smartprospect');
|
|
718
|
+
try {
|
|
719
|
+
const client = (0, smartprospect_1.createSmartProspectClient)(config.smartprospect);
|
|
720
|
+
if (!client) {
|
|
721
|
+
result.error = 'Failed to create SmartProspect client';
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
// Build search filters
|
|
725
|
+
const filters = buildSmartProspectFiltersFromLinkedIn(contact);
|
|
726
|
+
const searchFilters = {
|
|
727
|
+
firstName: filters.firstName,
|
|
728
|
+
lastName: filters.lastName,
|
|
729
|
+
limit: 25,
|
|
730
|
+
};
|
|
731
|
+
if (includeCompany && filters.companyName) {
|
|
732
|
+
searchFilters.companyName = filters.companyName;
|
|
733
|
+
}
|
|
734
|
+
// Search for matching contacts
|
|
735
|
+
const searchResponse = await client.search(searchFilters);
|
|
736
|
+
if (!searchResponse.success || searchResponse.data.list.length === 0) {
|
|
737
|
+
// Try broader search without company
|
|
738
|
+
if (includeCompany && filters.companyName) {
|
|
739
|
+
const broaderResponse = await client.search({
|
|
740
|
+
firstName: filters.firstName,
|
|
741
|
+
lastName: filters.lastName,
|
|
742
|
+
limit: 25,
|
|
743
|
+
});
|
|
744
|
+
if (broaderResponse.success && broaderResponse.data.list.length > 0) {
|
|
745
|
+
searchResponse.data.list = broaderResponse.data.list;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (searchResponse.data.list.length === 0) {
|
|
750
|
+
result.error = result.error
|
|
751
|
+
? `${result.error}; SmartProspect: No candidates found`
|
|
752
|
+
: 'SmartProspect: No candidates found';
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
// Find best match
|
|
756
|
+
const matchResult = findBestMatch(contact, searchResponse.data.list, {
|
|
757
|
+
minConfidence: minMatchConfidence,
|
|
758
|
+
fuzzyNames: true,
|
|
759
|
+
fuzzyCompany: true,
|
|
760
|
+
});
|
|
761
|
+
if (!matchResult) {
|
|
762
|
+
result.error = result.error
|
|
763
|
+
? `${result.error}; SmartProspect: No match above ${minMatchConfidence}% confidence`
|
|
764
|
+
: `SmartProspect: No match above ${minMatchConfidence}% confidence`;
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
// Fetch email for matched contact (COSTS CREDITS)
|
|
768
|
+
const fetchResponse = await client.fetch([matchResult.smartProspectContact.id]);
|
|
769
|
+
if (fetchResponse.success && fetchResponse.data.list.length > 0) {
|
|
770
|
+
const enrichedContact = fetchResponse.data.list[0];
|
|
771
|
+
if (enrichedContact.email) {
|
|
772
|
+
result.emails.push({
|
|
773
|
+
email: enrichedContact.email,
|
|
774
|
+
source: 'smartprospect',
|
|
775
|
+
confidence: matchResult.confidence,
|
|
776
|
+
type: 'business', // SmartProspect returns business emails
|
|
777
|
+
verified: enrichedContact.verificationStatus === 'verified',
|
|
778
|
+
deliverability: enrichedContact.emailDeliverability,
|
|
779
|
+
metadata: {
|
|
780
|
+
matchQuality: matchResult.quality,
|
|
781
|
+
matchedFields: matchResult.matchedFields,
|
|
782
|
+
smartProspectId: enrichedContact.id,
|
|
783
|
+
company: enrichedContact.company?.name,
|
|
784
|
+
title: enrichedContact.title,
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
result.success = true;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
result.error = result.error
|
|
793
|
+
? `${result.error}; SmartProspect error: ${err instanceof Error ? err.message : 'Unknown error'}`
|
|
794
|
+
: `SmartProspect error: ${err instanceof Error ? err.message : 'Unknown error'}`;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// Sort emails by confidence (highest first)
|
|
798
|
+
result.emails.sort((a, b) => b.confidence - a.confidence);
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Get emails for multiple LinkedIn contacts in batch
|
|
803
|
+
*
|
|
804
|
+
* Processes contacts sequentially to avoid rate limits.
|
|
805
|
+
*/
|
|
806
|
+
async function getEmailsForLinkedInContactsBatch(contacts, config, options = {}) {
|
|
807
|
+
const { delayMs = 200, onProgress, ...lookupOptions } = options;
|
|
808
|
+
const results = [];
|
|
809
|
+
for (let i = 0; i < contacts.length; i++) {
|
|
810
|
+
const result = await getEmailsForLinkedInContact(contacts[i], config, lookupOptions);
|
|
811
|
+
results.push(result);
|
|
812
|
+
if (onProgress) {
|
|
813
|
+
onProgress(i + 1, contacts.length, result);
|
|
814
|
+
}
|
|
815
|
+
// Delay between requests (except for last one)
|
|
816
|
+
if (i < contacts.length - 1 && delayMs > 0) {
|
|
817
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return results;
|
|
821
|
+
}
|