linkedin-secret-sauce 0.5.0 → 0.5.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.
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Authentication modules for enrichment providers
3
+ */
4
+ export { getSmartLeadToken, getSmartLeadUser, clearSmartLeadToken, clearAllSmartLeadTokens, getSmartLeadTokenCacheStats, type SmartLeadCredentials, type SmartLeadAuthConfig, type SmartLeadUser, type SmartLeadLoginResponse, } from './smartlead-auth';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication modules for enrichment providers
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSmartLeadTokenCacheStats = exports.clearAllSmartLeadTokens = exports.clearSmartLeadToken = exports.getSmartLeadUser = exports.getSmartLeadToken = void 0;
7
+ var smartlead_auth_1 = require("./smartlead-auth");
8
+ Object.defineProperty(exports, "getSmartLeadToken", { enumerable: true, get: function () { return smartlead_auth_1.getSmartLeadToken; } });
9
+ Object.defineProperty(exports, "getSmartLeadUser", { enumerable: true, get: function () { return smartlead_auth_1.getSmartLeadUser; } });
10
+ Object.defineProperty(exports, "clearSmartLeadToken", { enumerable: true, get: function () { return smartlead_auth_1.clearSmartLeadToken; } });
11
+ Object.defineProperty(exports, "clearAllSmartLeadTokens", { enumerable: true, get: function () { return smartlead_auth_1.clearAllSmartLeadTokens; } });
12
+ Object.defineProperty(exports, "getSmartLeadTokenCacheStats", { enumerable: true, get: function () { return smartlead_auth_1.getSmartLeadTokenCacheStats; } });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * SmartLead Authentication Client
3
+ *
4
+ * Handles JWT token acquisition via email/password login.
5
+ * Tokens are cached in-memory with automatic refresh before expiry.
6
+ */
7
+ export interface SmartLeadCredentials {
8
+ email: string;
9
+ password: string;
10
+ }
11
+ export interface SmartLeadAuthConfig {
12
+ credentials: SmartLeadCredentials;
13
+ /** Login API URL (default: https://server.smartlead.ai/api/auth/login) */
14
+ loginUrl?: string;
15
+ /** Token refresh buffer in ms - refresh this much before expiry (default: 5 min) */
16
+ refreshBufferMs?: number;
17
+ }
18
+ export interface SmartLeadUser {
19
+ id: number;
20
+ uuid: string;
21
+ email: string;
22
+ name: string;
23
+ role: string;
24
+ api_key: string;
25
+ region: string;
26
+ }
27
+ export interface SmartLeadLoginResponse {
28
+ user: SmartLeadUser;
29
+ token: string;
30
+ }
31
+ export declare function getSmartLeadToken(config: SmartLeadAuthConfig): Promise<string>;
32
+ /**
33
+ * Get SmartLead user info (from cached login)
34
+ */
35
+ export declare function getSmartLeadUser(config: SmartLeadAuthConfig): Promise<SmartLeadUser>;
36
+ /**
37
+ * Force clear cached token (e.g., on 401 error)
38
+ */
39
+ export declare function clearSmartLeadToken(email: string): void;
40
+ /**
41
+ * Clear all cached tokens
42
+ */
43
+ export declare function clearAllSmartLeadTokens(): void;
44
+ /**
45
+ * Get token cache stats (for debugging)
46
+ */
47
+ export declare function getSmartLeadTokenCacheStats(): {
48
+ cachedEmails: string[];
49
+ totalCached: number;
50
+ };
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * SmartLead Authentication Client
4
+ *
5
+ * Handles JWT token acquisition via email/password login.
6
+ * Tokens are cached in-memory with automatic refresh before expiry.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getSmartLeadToken = getSmartLeadToken;
10
+ exports.getSmartLeadUser = getSmartLeadUser;
11
+ exports.clearSmartLeadToken = clearSmartLeadToken;
12
+ exports.clearAllSmartLeadTokens = clearAllSmartLeadTokens;
13
+ exports.getSmartLeadTokenCacheStats = getSmartLeadTokenCacheStats;
14
+ const DEFAULT_LOGIN_URL = 'https://server.smartlead.ai/api/auth/login';
15
+ const DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
16
+ const TOKEN_LIFETIME_MS = 24 * 60 * 60 * 1000; // Assume 24h JWT lifetime (conservative)
17
+ // In-memory token cache keyed by email
18
+ const tokenCache = new Map();
19
+ /**
20
+ * Parse JWT to extract expiration time
21
+ */
22
+ function parseJwtExpiry(token) {
23
+ try {
24
+ const parts = token.split('.');
25
+ if (parts.length !== 3)
26
+ return null;
27
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf8'));
28
+ if (payload.exp) {
29
+ return payload.exp * 1000; // Convert to ms
30
+ }
31
+ return null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ /**
38
+ * Login to SmartLead and obtain JWT token
39
+ */
40
+ async function login(config) {
41
+ const { credentials, loginUrl = DEFAULT_LOGIN_URL } = config;
42
+ const response = await fetch(loginUrl, {
43
+ method: 'POST',
44
+ headers: {
45
+ Accept: 'application/json, text/plain, */*',
46
+ 'Content-Type': 'application/json',
47
+ Origin: 'https://app.smartlead.ai',
48
+ Referer: 'https://app.smartlead.ai/',
49
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
50
+ },
51
+ body: JSON.stringify({
52
+ email: credentials.email,
53
+ password: credentials.password,
54
+ }),
55
+ });
56
+ if (!response.ok) {
57
+ const errorText = await response.text().catch(() => '');
58
+ throw new Error(`SmartLead login failed: ${response.status} - ${errorText}`);
59
+ }
60
+ const data = (await response.json());
61
+ if (!data.token || !data.user) {
62
+ throw new Error('SmartLead login response missing token or user');
63
+ }
64
+ const now = Date.now();
65
+ // Try to parse expiry from JWT, fallback to assumed lifetime
66
+ let expiresAt = parseJwtExpiry(data.token);
67
+ if (!expiresAt) {
68
+ expiresAt = now + TOKEN_LIFETIME_MS;
69
+ }
70
+ return {
71
+ token: data.token,
72
+ user: data.user,
73
+ expiresAt,
74
+ obtainedAt: now,
75
+ };
76
+ }
77
+ /**
78
+ * Check if cached token is still valid (with buffer for refresh)
79
+ */
80
+ function isTokenValid(cached, refreshBufferMs) {
81
+ const now = Date.now();
82
+ return cached.expiresAt - refreshBufferMs > now;
83
+ }
84
+ /**
85
+ * Get a valid SmartLead JWT token
86
+ *
87
+ * Returns cached token if still valid, otherwise performs fresh login.
88
+ * Thread-safe via promise deduplication.
89
+ */
90
+ const pendingLogins = new Map();
91
+ async function getSmartLeadToken(config) {
92
+ const { credentials, refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS } = config;
93
+ const cacheKey = credentials.email.toLowerCase();
94
+ // Check cache first
95
+ const cached = tokenCache.get(cacheKey);
96
+ if (cached && isTokenValid(cached, refreshBufferMs)) {
97
+ return cached.token;
98
+ }
99
+ // Check if login already in progress (dedup concurrent requests)
100
+ const pending = pendingLogins.get(cacheKey);
101
+ if (pending) {
102
+ const result = await pending;
103
+ return result.token;
104
+ }
105
+ // Perform login
106
+ const loginPromise = login(config);
107
+ pendingLogins.set(cacheKey, loginPromise);
108
+ try {
109
+ const result = await loginPromise;
110
+ tokenCache.set(cacheKey, result);
111
+ return result.token;
112
+ }
113
+ finally {
114
+ pendingLogins.delete(cacheKey);
115
+ }
116
+ }
117
+ /**
118
+ * Get SmartLead user info (from cached login)
119
+ */
120
+ async function getSmartLeadUser(config) {
121
+ const { credentials, refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS } = config;
122
+ const cacheKey = credentials.email.toLowerCase();
123
+ // Check cache first
124
+ const cached = tokenCache.get(cacheKey);
125
+ if (cached && isTokenValid(cached, refreshBufferMs)) {
126
+ return cached.user;
127
+ }
128
+ // Perform login to get fresh user info
129
+ await getSmartLeadToken(config);
130
+ const freshCached = tokenCache.get(cacheKey);
131
+ if (!freshCached) {
132
+ throw new Error('SmartLead auth: failed to obtain user info');
133
+ }
134
+ return freshCached.user;
135
+ }
136
+ /**
137
+ * Force clear cached token (e.g., on 401 error)
138
+ */
139
+ function clearSmartLeadToken(email) {
140
+ tokenCache.delete(email.toLowerCase());
141
+ }
142
+ /**
143
+ * Clear all cached tokens
144
+ */
145
+ function clearAllSmartLeadTokens() {
146
+ tokenCache.clear();
147
+ }
148
+ /**
149
+ * Get token cache stats (for debugging)
150
+ */
151
+ function getSmartLeadTokenCacheStats() {
152
+ return {
153
+ cachedEmails: Array.from(tokenCache.keys()),
154
+ totalCached: tokenCache.size,
155
+ };
156
+ }
@@ -26,7 +26,7 @@
26
26
  * });
27
27
  * ```
28
28
  */
29
- import type { EnrichmentClientConfig, EnrichmentClient } from './types';
29
+ import type { EnrichmentClientConfig, EnrichmentClient } from "./types";
30
30
  /**
31
31
  * Create an enrichment client with the given configuration
32
32
  *
@@ -34,10 +34,11 @@ import type { EnrichmentClientConfig, EnrichmentClient } from './types';
34
34
  * @returns An enrichment client with enrich and enrichBatch methods
35
35
  */
36
36
  export declare function createEnrichmentClient(config: EnrichmentClientConfig): EnrichmentClient;
37
- export * from './types';
38
- export { isPersonalEmail, isBusinessEmail, isPersonalDomain, PERSONAL_DOMAINS, } from './utils/personal-domains';
39
- export { isDisposableEmail, isDisposableDomain, DISPOSABLE_DOMAINS, } from './utils/disposable-domains';
40
- export { isValidEmailSyntax, isRoleAccount, asciiFold, cleanNamePart, hostnameFromUrl, extractLinkedInUsername, } from './utils/validation';
41
- export { verifyEmailMx } from './verification/mx';
42
- export { createConstructProvider, createLddProvider, createSmartProspectProvider, createHunterProvider, createApolloProvider, createDropcontactProvider, } from './providers';
43
- export { enrichBusinessEmail, enrichBatch } from './orchestrator';
37
+ export * from "./types";
38
+ export { isPersonalEmail, isBusinessEmail, isPersonalDomain, PERSONAL_DOMAINS, } from "./utils/personal-domains";
39
+ export { isDisposableEmail, isDisposableDomain, DISPOSABLE_DOMAINS, } from "./utils/disposable-domains";
40
+ export { isValidEmailSyntax, isRoleAccount, asciiFold, cleanNamePart, hostnameFromUrl, extractLinkedInUsername, } from "./utils/validation";
41
+ export { verifyEmailMx } from "./verification/mx";
42
+ export { createConstructProvider, createLddProvider, createSmartProspectProvider, createHunterProvider, createApolloProvider, createDropcontactProvider, } from "./providers";
43
+ export { enrichBusinessEmail, enrichBatch } from "./orchestrator";
44
+ export { getSmartLeadToken, getSmartLeadUser, clearSmartLeadToken, clearAllSmartLeadTokens, getSmartLeadTokenCacheStats, type SmartLeadCredentials, type SmartLeadAuthConfig, type SmartLeadUser, type SmartLeadLoginResponse, } from "./auth";
@@ -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.enrichBatch = exports.enrichBusinessEmail = 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.getSmartLeadTokenCacheStats = exports.clearAllSmartLeadTokens = exports.clearSmartLeadToken = exports.getSmartLeadUser = exports.getSmartLeadToken = exports.enrichBatch = exports.enrichBusinessEmail = 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");
@@ -55,12 +55,12 @@ const dropcontact_1 = require("./providers/dropcontact");
55
55
  * Default provider order
56
56
  */
57
57
  const DEFAULT_ORDER = [
58
- 'construct',
59
- 'ldd',
60
- 'smartprospect',
61
- 'hunter',
62
- 'apollo',
63
- 'dropcontact',
58
+ "construct",
59
+ "ldd",
60
+ "smartprospect",
61
+ "hunter",
62
+ "apollo",
63
+ "dropcontact",
64
64
  ];
65
65
  /**
66
66
  * Create an enrichment client with the given configuration
@@ -69,26 +69,26 @@ const DEFAULT_ORDER = [
69
69
  * @returns An enrichment client with enrich and enrichBatch methods
70
70
  */
71
71
  function createEnrichmentClient(config) {
72
- const { providers: providerConfigs, options = {}, cache, onCost, logger } = config;
72
+ const { providers: providerConfigs, options = {}, cache, onCost, logger, } = config;
73
73
  // Build provider functions based on configuration
74
74
  const providerFuncs = new Map();
75
75
  // Always create construct provider (free, no API key needed)
76
- providerFuncs.set('construct', (0, construct_1.createConstructProvider)(providerConfigs.construct));
76
+ providerFuncs.set("construct", (0, construct_1.createConstructProvider)(providerConfigs.construct));
77
77
  // Create other providers if configured
78
78
  if (providerConfigs.ldd) {
79
- providerFuncs.set('ldd', (0, ldd_1.createLddProvider)(providerConfigs.ldd));
79
+ providerFuncs.set("ldd", (0, ldd_1.createLddProvider)(providerConfigs.ldd));
80
80
  }
81
81
  if (providerConfigs.smartprospect) {
82
- providerFuncs.set('smartprospect', (0, smartprospect_1.createSmartProspectProvider)(providerConfigs.smartprospect));
82
+ providerFuncs.set("smartprospect", (0, smartprospect_1.createSmartProspectProvider)(providerConfigs.smartprospect));
83
83
  }
84
84
  if (providerConfigs.hunter) {
85
- providerFuncs.set('hunter', (0, hunter_1.createHunterProvider)(providerConfigs.hunter));
85
+ providerFuncs.set("hunter", (0, hunter_1.createHunterProvider)(providerConfigs.hunter));
86
86
  }
87
87
  if (providerConfigs.apollo) {
88
- providerFuncs.set('apollo', (0, apollo_1.createApolloProvider)(providerConfigs.apollo));
88
+ providerFuncs.set("apollo", (0, apollo_1.createApolloProvider)(providerConfigs.apollo));
89
89
  }
90
90
  if (providerConfigs.dropcontact) {
91
- providerFuncs.set('dropcontact', (0, dropcontact_1.createDropcontactProvider)(providerConfigs.dropcontact));
91
+ providerFuncs.set("dropcontact", (0, dropcontact_1.createDropcontactProvider)(providerConfigs.dropcontact));
92
92
  }
93
93
  // Build ordered provider list
94
94
  const providerOrder = options.providerOrder ?? DEFAULT_ORDER;
@@ -120,7 +120,7 @@ function createEnrichmentClient(config) {
120
120
  try {
121
121
  const cached = await cache.get(cacheKey);
122
122
  if (cached) {
123
- logger?.debug?.('enrichment.cache.hit', { cacheKey });
123
+ logger?.debug?.("enrichment.cache.hit", { cacheKey });
124
124
  return cached;
125
125
  }
126
126
  }
@@ -176,10 +176,16 @@ function buildCacheKey(candidate) {
176
176
  }
177
177
  else {
178
178
  // Fall back to name + domain/company
179
- const firstName = candidate.firstName || candidate.first_name || candidate.first || '';
180
- const lastName = candidate.lastName || candidate.last_name || candidate.last || '';
181
- const domain = candidate.domain || candidate.companyDomain || candidate.company_domain || '';
182
- const company = candidate.company || candidate.currentCompany || candidate.organization || '';
179
+ const firstName = candidate.firstName || candidate.first_name || candidate.first || "";
180
+ const lastName = candidate.lastName || candidate.last_name || candidate.last || "";
181
+ const domain = candidate.domain ||
182
+ candidate.companyDomain ||
183
+ candidate.company_domain ||
184
+ "";
185
+ const company = candidate.company ||
186
+ candidate.currentCompany ||
187
+ candidate.organization ||
188
+ "";
183
189
  if (firstName && (domain || company)) {
184
190
  parts.push(`name:${firstName.toLowerCase()}`);
185
191
  if (lastName)
@@ -193,7 +199,7 @@ function buildCacheKey(candidate) {
193
199
  if (parts.length === 0) {
194
200
  return null;
195
201
  }
196
- return parts.join('|');
202
+ return parts.join("|");
197
203
  }
198
204
  // Re-export types
199
205
  __exportStar(require("./types"), exports);
@@ -229,3 +235,10 @@ Object.defineProperty(exports, "createDropcontactProvider", { enumerable: true,
229
235
  var orchestrator_2 = require("./orchestrator");
230
236
  Object.defineProperty(exports, "enrichBusinessEmail", { enumerable: true, get: function () { return orchestrator_2.enrichBusinessEmail; } });
231
237
  Object.defineProperty(exports, "enrichBatch", { enumerable: true, get: function () { return orchestrator_2.enrichBatch; } });
238
+ // Re-export auth utilities (for advanced usage)
239
+ var auth_1 = require("./auth");
240
+ Object.defineProperty(exports, "getSmartLeadToken", { enumerable: true, get: function () { return auth_1.getSmartLeadToken; } });
241
+ Object.defineProperty(exports, "getSmartLeadUser", { enumerable: true, get: function () { return auth_1.getSmartLeadUser; } });
242
+ Object.defineProperty(exports, "clearSmartLeadToken", { enumerable: true, get: function () { return auth_1.clearSmartLeadToken; } });
243
+ Object.defineProperty(exports, "clearAllSmartLeadTokens", { enumerable: true, get: function () { return auth_1.clearAllSmartLeadTokens; } });
244
+ Object.defineProperty(exports, "getSmartLeadTokenCacheStats", { enumerable: true, get: function () { return auth_1.getSmartLeadTokenCacheStats; } });
@@ -3,9 +3,17 @@
3
3
  *
4
4
  * Smartlead's prospect database - a REVERSE-ENGINEERED private API.
5
5
  * Two-phase process: search (free) then fetch (costs credits).
6
+ *
7
+ * Supports two authentication methods:
8
+ * 1. Direct token: Pass `apiToken` directly (for pre-authenticated scenarios)
9
+ * 2. Credentials: Pass `email` and `password` for automatic login with token caching
6
10
  */
7
- import type { EnrichmentCandidate, ProviderResult, SmartProspectConfig } from '../types';
11
+ import type { EnrichmentCandidate, ProviderResult, SmartProspectConfig } from "../types";
8
12
  /**
9
13
  * Create the SmartProspect provider function
14
+ *
15
+ * Supports two auth methods:
16
+ * 1. Direct token: Pass `apiToken` in config
17
+ * 2. Credentials: Pass `email` and `password` in config for auto-login
10
18
  */
11
19
  export declare function createSmartProspectProvider(config: SmartProspectConfig): (candidate: EnrichmentCandidate) => Promise<ProviderResult | null>;
@@ -4,10 +4,15 @@
4
4
  *
5
5
  * Smartlead's prospect database - a REVERSE-ENGINEERED private API.
6
6
  * Two-phase process: search (free) then fetch (costs credits).
7
+ *
8
+ * Supports two authentication methods:
9
+ * 1. Direct token: Pass `apiToken` directly (for pre-authenticated scenarios)
10
+ * 2. Credentials: Pass `email` and `password` for automatic login with token caching
7
11
  */
8
12
  Object.defineProperty(exports, "__esModule", { value: true });
9
13
  exports.createSmartProspectProvider = createSmartProspectProvider;
10
- const DEFAULT_API_URL = 'https://prospect-api.smartlead.ai/api/search-email-leads';
14
+ const smartlead_auth_1 = require("../auth/smartlead-auth");
15
+ const DEFAULT_API_URL = "https://prospect-api.smartlead.ai/api/search-email-leads";
11
16
  /**
12
17
  * Delay helper for retry logic
13
18
  */
@@ -28,7 +33,7 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
28
33
  continue;
29
34
  }
30
35
  if (!res.ok) {
31
- const errorText = await res.text().catch(() => '');
36
+ const errorText = await res.text().catch(() => "");
32
37
  throw new Error(`SmartProspect API error: ${res.status} - ${errorText}`);
33
38
  }
34
39
  return (await res.json());
@@ -41,7 +46,7 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
41
46
  }
42
47
  }
43
48
  }
44
- throw lastErr ?? new Error('smartprospect_http_error');
49
+ throw lastErr ?? new Error("smartprospect_http_error");
45
50
  }
46
51
  /**
47
52
  * Calculate match score between search input and contact result
@@ -49,30 +54,33 @@ async function requestWithRetry(url, options, retries = 2, backoffMs = 300) {
49
54
  function calculateMatchScore(contact, searchName, company, title) {
50
55
  let score = 0;
51
56
  // Name match scoring (0-50 points)
52
- const contactName = contact.fullName?.toLowerCase() || '';
57
+ const contactName = contact.fullName?.toLowerCase() || "";
53
58
  const searchNameLower = searchName.toLowerCase();
54
59
  if (contactName === searchNameLower) {
55
60
  score += 50; // Exact match
56
61
  }
57
- else if (contactName.includes(searchNameLower) || searchNameLower.includes(contactName)) {
62
+ else if (contactName.includes(searchNameLower) ||
63
+ searchNameLower.includes(contactName)) {
58
64
  score += 30; // Partial match
59
65
  }
60
66
  // Company match scoring (0-30 points)
61
67
  if (company) {
62
- const contactCompany = contact.company?.name?.toLowerCase() || '';
68
+ const contactCompany = contact.company?.name?.toLowerCase() || "";
63
69
  const searchCompany = company.toLowerCase();
64
70
  if (contactCompany === searchCompany) {
65
71
  score += 30; // Exact match
66
72
  }
67
- else if (contactCompany.includes(searchCompany) || searchCompany.includes(contactCompany)) {
73
+ else if (contactCompany.includes(searchCompany) ||
74
+ searchCompany.includes(contactCompany)) {
68
75
  score += 15; // Partial match
69
76
  }
70
77
  }
71
78
  // Title match scoring (0-10 points)
72
79
  if (title) {
73
- const contactTitle = contact.title?.toLowerCase() || '';
80
+ const contactTitle = contact.title?.toLowerCase() || "";
74
81
  const searchTitle = title.toLowerCase();
75
- if (contactTitle.includes(searchTitle) || searchTitle.includes(contactTitle)) {
82
+ if (contactTitle.includes(searchTitle) ||
83
+ searchTitle.includes(contactTitle)) {
76
84
  score += 10;
77
85
  }
78
86
  }
@@ -89,37 +97,72 @@ function extractNames(candidate) {
89
97
  const firstName = candidate.firstName ||
90
98
  candidate.first_name ||
91
99
  candidate.first ||
92
- candidate.name?.split(' ')?.[0] ||
93
- '';
100
+ candidate.name?.split(" ")?.[0] ||
101
+ "";
94
102
  const lastName = candidate.lastName ||
95
103
  candidate.last_name ||
96
104
  candidate.last ||
97
- candidate.name?.split(' ')?.slice(1).join(' ') ||
98
- '';
105
+ candidate.name?.split(" ")?.slice(1).join(" ") ||
106
+ "";
99
107
  return { firstName, lastName };
100
108
  }
101
109
  /**
102
110
  * Create the SmartProspect provider function
111
+ *
112
+ * Supports two auth methods:
113
+ * 1. Direct token: Pass `apiToken` in config
114
+ * 2. Credentials: Pass `email` and `password` in config for auto-login
103
115
  */
104
116
  function createSmartProspectProvider(config) {
105
- const { apiToken, apiUrl = DEFAULT_API_URL } = config;
106
- if (!apiToken) {
117
+ const { apiToken, email, password, apiUrl = DEFAULT_API_URL, loginUrl, eagerInit = true, } = config;
118
+ // Check if we have valid auth config
119
+ const hasDirectToken = !!apiToken;
120
+ const hasCredentials = !!email && !!password;
121
+ if (!hasDirectToken && !hasCredentials) {
107
122
  // Return a no-op provider if not configured
108
123
  const noopProvider = async () => null;
109
- noopProvider.__name = 'smartprospect';
124
+ noopProvider.__name = "smartprospect";
110
125
  return noopProvider;
111
126
  }
127
+ /**
128
+ * Get the current auth token (either direct or via login)
129
+ */
130
+ async function getAuthToken() {
131
+ if (hasDirectToken) {
132
+ return apiToken;
133
+ }
134
+ // Use credentials-based auth with token caching
135
+ return (0, smartlead_auth_1.getSmartLeadToken)({
136
+ credentials: { email: email, password: password },
137
+ loginUrl,
138
+ });
139
+ }
140
+ // Eager initialization: pre-fetch token so it's ready for first request
141
+ if (eagerInit && hasCredentials) {
142
+ // Fire and forget - don't block provider creation
143
+ getAuthToken().catch(() => {
144
+ // Silently ignore errors during eager init - will retry on first request
145
+ });
146
+ }
147
+ /**
148
+ * Handle 401 errors by clearing cached token
149
+ */
150
+ async function handleAuthError() {
151
+ if (hasCredentials) {
152
+ (0, smartlead_auth_1.clearSmartLeadToken)(email);
153
+ }
154
+ }
112
155
  /**
113
156
  * Search for contacts matching filters (FREE - no credits used)
114
157
  */
115
158
  async function searchContacts(filters) {
116
- try {
117
- const response = await requestWithRetry(`${apiUrl}/search-contacts`, {
118
- method: 'POST',
159
+ const makeRequest = async (token) => {
160
+ return requestWithRetry(`${apiUrl}/search-contacts`, {
161
+ method: "POST",
119
162
  headers: {
120
- Authorization: `Bearer ${apiToken}`,
121
- 'Content-Type': 'application/json',
122
- Accept: 'application/json',
163
+ Authorization: `Bearer ${token}`,
164
+ "Content-Type": "application/json",
165
+ Accept: "application/json",
123
166
  },
124
167
  body: JSON.stringify({
125
168
  ...filters,
@@ -128,12 +171,28 @@ function createSmartProspectProvider(config) {
128
171
  titleExactMatch: filters.titleExactMatch ?? false,
129
172
  }),
130
173
  });
131
- return response;
174
+ };
175
+ try {
176
+ const token = await getAuthToken();
177
+ return await makeRequest(token);
132
178
  }
133
- catch {
179
+ catch (err) {
180
+ // On 401, clear token and retry once with fresh token
181
+ if (err instanceof Error &&
182
+ err.message.includes("401") &&
183
+ hasCredentials) {
184
+ await handleAuthError();
185
+ try {
186
+ const freshToken = await getAuthToken();
187
+ return await makeRequest(freshToken);
188
+ }
189
+ catch {
190
+ // Retry failed too
191
+ }
192
+ }
134
193
  return {
135
194
  success: false,
136
- message: 'Search failed',
195
+ message: "Search failed",
137
196
  data: { list: [], total_count: 0 },
138
197
  };
139
198
  }
@@ -142,22 +201,38 @@ function createSmartProspectProvider(config) {
142
201
  * Fetch/enrich emails for specific contact IDs (COSTS CREDITS)
143
202
  */
144
203
  async function fetchContacts(contactIds) {
145
- try {
146
- const response = await requestWithRetry(`${apiUrl}/fetch-contacts`, {
147
- method: 'POST',
204
+ const makeRequest = async (token) => {
205
+ return requestWithRetry(`${apiUrl}/fetch-contacts`, {
206
+ method: "POST",
148
207
  headers: {
149
- Authorization: `Bearer ${apiToken}`,
150
- 'Content-Type': 'application/json',
151
- Accept: 'application/json',
208
+ Authorization: `Bearer ${token}`,
209
+ "Content-Type": "application/json",
210
+ Accept: "application/json",
152
211
  },
153
212
  body: JSON.stringify({ contactIds }),
154
213
  });
155
- return response;
214
+ };
215
+ try {
216
+ const token = await getAuthToken();
217
+ return await makeRequest(token);
156
218
  }
157
- catch {
219
+ catch (err) {
220
+ // On 401, clear token and retry once with fresh token
221
+ if (err instanceof Error &&
222
+ err.message.includes("401") &&
223
+ hasCredentials) {
224
+ await handleAuthError();
225
+ try {
226
+ const freshToken = await getAuthToken();
227
+ return await makeRequest(freshToken);
228
+ }
229
+ catch {
230
+ // Retry failed too
231
+ }
232
+ }
158
233
  return {
159
234
  success: false,
160
- message: 'Fetch failed',
235
+ message: "Fetch failed",
161
236
  data: {
162
237
  list: [],
163
238
  total_count: 0,
@@ -183,9 +258,18 @@ function createSmartProspectProvider(config) {
183
258
  return null; // Minimum requirement
184
259
  }
185
260
  const fullName = `${firstName} ${lastName}`.trim();
186
- const company = candidate.company || candidate.currentCompany || candidate.organization || undefined;
187
- const title = candidate.title || candidate.currentRole || candidate.current_role || undefined;
188
- const domain = candidate.domain || candidate.companyDomain || candidate.company_domain || undefined;
261
+ const company = candidate.company ||
262
+ candidate.currentCompany ||
263
+ candidate.organization ||
264
+ undefined;
265
+ const title = candidate.title ||
266
+ candidate.currentRole ||
267
+ candidate.current_role ||
268
+ undefined;
269
+ const domain = candidate.domain ||
270
+ candidate.companyDomain ||
271
+ candidate.company_domain ||
272
+ undefined;
189
273
  // Build search filters
190
274
  const filters = {
191
275
  name: [fullName],
@@ -227,8 +311,8 @@ function createSmartProspectProvider(config) {
227
311
  return null;
228
312
  }
229
313
  // Calculate verification confidence
230
- const isVerified = enrichedContact.verificationStatus === 'valid';
231
- const isCatchAll = enrichedContact.verificationStatus === 'catch_all';
314
+ const isVerified = enrichedContact.verificationStatus === "valid";
315
+ const isCatchAll = enrichedContact.verificationStatus === "catch_all";
232
316
  const deliverability = enrichedContact.emailDeliverability || 0;
233
317
  let confidence = deliverability * 100;
234
318
  if (isVerified) {
@@ -244,6 +328,6 @@ function createSmartProspectProvider(config) {
244
328
  };
245
329
  }
246
330
  // Mark provider name for orchestrator
247
- fetchEmail.__name = 'smartprospect';
331
+ fetchEmail.__name = "smartprospect";
248
332
  return fetchEmail;
249
333
  }
@@ -78,10 +78,24 @@ export interface ApolloConfig {
78
78
  }
79
79
  /**
80
80
  * SmartProspect/Smartlead provider configuration
81
+ *
82
+ * Supports two auth methods:
83
+ * 1. Direct token: Pass `apiToken` directly
84
+ * 2. Credentials: Pass `email` and `password` for auto-login with token caching
81
85
  */
82
86
  export interface SmartProspectConfig {
83
- apiToken: string;
87
+ /** Direct API/JWT token (if you already have one) */
88
+ apiToken?: string;
89
+ /** SmartLead account email (for credentials-based auth) */
90
+ email?: string;
91
+ /** SmartLead account password (for credentials-based auth) */
92
+ password?: string;
93
+ /** API URL override (default: https://prospect-api.smartlead.ai/api/search-email-leads) */
84
94
  apiUrl?: string;
95
+ /** Login URL override (default: https://server.smartlead.ai/api/auth/login) */
96
+ loginUrl?: string;
97
+ /** Pre-fetch token on client creation (default: true) - ensures token is ready for first request */
98
+ eagerInit?: boolean;
85
99
  }
86
100
  /**
87
101
  * LinkedIn Data Dump provider configuration
@@ -189,7 +203,7 @@ export interface EnrichmentClient {
189
203
  /**
190
204
  * Available provider names
191
205
  */
192
- export type ProviderName = 'construct' | 'ldd' | 'smartprospect' | 'hunter' | 'apollo' | 'dropcontact';
206
+ export type ProviderName = "construct" | "ldd" | "smartprospect" | "hunter" | "apollo" | "dropcontact";
193
207
  /**
194
208
  * Default provider order
195
209
  */
@@ -207,7 +221,7 @@ export interface VerificationResult {
207
221
  /** Confidence score 0-100 */
208
222
  confidence: number;
209
223
  /** Reason for the result */
210
- reason?: 'valid' | 'invalid' | 'syntax' | 'mx_missing' | 'disposable' | 'role_account' | 'catch_all' | 'timeout' | 'error';
224
+ reason?: "valid" | "invalid" | "syntax" | "mx_missing" | "disposable" | "role_account" | "catch_all" | "timeout" | "error";
211
225
  /** Whether this is a catch-all domain */
212
226
  isCatchAll: boolean;
213
227
  /** Whether this is a role account (info@, support@, etc.) */
@@ -11,12 +11,12 @@ exports.PROVIDER_COSTS = exports.DEFAULT_PROVIDER_ORDER = void 0;
11
11
  * Default provider order
12
12
  */
13
13
  exports.DEFAULT_PROVIDER_ORDER = [
14
- 'construct',
15
- 'ldd',
16
- 'smartprospect',
17
- 'hunter',
18
- 'apollo',
19
- 'dropcontact',
14
+ "construct",
15
+ "ldd",
16
+ "smartprospect",
17
+ "hunter",
18
+ "apollo",
19
+ "dropcontact",
20
20
  ];
21
21
  /**
22
22
  * Provider costs in USD per lookup
package/dist/index.d.ts CHANGED
@@ -14,4 +14,5 @@ export type { RequestHistoryEntry } from "./utils/request-history";
14
14
  export * from "./constants";
15
15
  export { createEnrichmentClient } from "./enrichment";
16
16
  export type { CanonicalEmail, EnrichmentCandidate, ProviderResult, ProviderFunc, ProviderName, EnrichmentClientConfig, EnrichmentClient, ProvidersConfig, EnrichmentOptions, BatchEnrichmentOptions, HunterConfig, ApolloConfig, SmartProspectConfig, LddConfig, DropcontactConfig, ConstructConfig, CacheAdapter, CostCallback, EnrichmentLogger, VerificationResult, SmartProspectContact, SmartProspectSearchFilters, LddProfileData, LddApiResponse, } from "./enrichment";
17
- export { isPersonalEmail, isBusinessEmail, isPersonalDomain, isDisposableEmail, isDisposableDomain, isValidEmailSyntax, isRoleAccount, verifyEmailMx, PERSONAL_DOMAINS, DISPOSABLE_DOMAINS, DEFAULT_PROVIDER_ORDER, PROVIDER_COSTS, } from "./enrichment";
17
+ export { isPersonalEmail, isBusinessEmail, isPersonalDomain, isDisposableEmail, isDisposableDomain, isValidEmailSyntax, isRoleAccount, verifyEmailMx, PERSONAL_DOMAINS, DISPOSABLE_DOMAINS, DEFAULT_PROVIDER_ORDER, PROVIDER_COSTS, getSmartLeadToken, getSmartLeadUser, clearSmartLeadToken, clearAllSmartLeadTokens, getSmartLeadTokenCacheStats, } from "./enrichment";
18
+ export type { SmartLeadCredentials, SmartLeadAuthConfig, SmartLeadUser, SmartLeadLoginResponse, } from "./enrichment";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.PROVIDER_COSTS = exports.DEFAULT_PROVIDER_ORDER = exports.DISPOSABLE_DOMAINS = exports.PERSONAL_DOMAINS = exports.verifyEmailMx = exports.isRoleAccount = exports.isValidEmailSyntax = exports.isDisposableDomain = exports.isDisposableEmail = exports.isPersonalDomain = exports.isBusinessEmail = exports.isPersonalEmail = exports.createEnrichmentClient = exports.clearRequestHistory = exports.getRequestHistory = exports.parseSalesSearchResults = exports.parseFullProfile = exports.LinkedInClientError = exports.getConfig = exports.initializeLinkedInClient = void 0;
17
+ exports.getSmartLeadTokenCacheStats = exports.clearAllSmartLeadTokens = exports.clearSmartLeadToken = exports.getSmartLeadUser = exports.getSmartLeadToken = exports.PROVIDER_COSTS = exports.DEFAULT_PROVIDER_ORDER = exports.DISPOSABLE_DOMAINS = exports.PERSONAL_DOMAINS = exports.verifyEmailMx = exports.isRoleAccount = exports.isValidEmailSyntax = exports.isDisposableDomain = exports.isDisposableEmail = exports.isPersonalDomain = exports.isBusinessEmail = exports.isPersonalEmail = exports.createEnrichmentClient = exports.clearRequestHistory = exports.getRequestHistory = exports.parseSalesSearchResults = exports.parseFullProfile = exports.LinkedInClientError = exports.getConfig = exports.initializeLinkedInClient = void 0;
18
18
  var config_1 = require("./config");
19
19
  Object.defineProperty(exports, "initializeLinkedInClient", { enumerable: true, get: function () { return config_1.initializeLinkedInClient; } });
20
20
  Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return config_1.getConfig; } });
@@ -52,3 +52,9 @@ Object.defineProperty(exports, "PERSONAL_DOMAINS", { enumerable: true, get: func
52
52
  Object.defineProperty(exports, "DISPOSABLE_DOMAINS", { enumerable: true, get: function () { return enrichment_2.DISPOSABLE_DOMAINS; } });
53
53
  Object.defineProperty(exports, "DEFAULT_PROVIDER_ORDER", { enumerable: true, get: function () { return enrichment_2.DEFAULT_PROVIDER_ORDER; } });
54
54
  Object.defineProperty(exports, "PROVIDER_COSTS", { enumerable: true, get: function () { return enrichment_2.PROVIDER_COSTS; } });
55
+ // SmartLead Auth
56
+ Object.defineProperty(exports, "getSmartLeadToken", { enumerable: true, get: function () { return enrichment_2.getSmartLeadToken; } });
57
+ Object.defineProperty(exports, "getSmartLeadUser", { enumerable: true, get: function () { return enrichment_2.getSmartLeadUser; } });
58
+ Object.defineProperty(exports, "clearSmartLeadToken", { enumerable: true, get: function () { return enrichment_2.clearSmartLeadToken; } });
59
+ Object.defineProperty(exports, "clearAllSmartLeadTokens", { enumerable: true, get: function () { return enrichment_2.clearAllSmartLeadTokens; } });
60
+ Object.defineProperty(exports, "getSmartLeadTokenCacheStats", { enumerable: true, get: function () { return enrichment_2.getSmartLeadTokenCacheStats; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkedin-secret-sauce",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Private LinkedIn Sales Navigator client with automatic cookie management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,20 +10,6 @@
10
10
  "publishConfig": {
11
11
  "registry": "https://registry.npmjs.org/"
12
12
  },
13
- "scripts": {
14
- "dev:playground": "pnpm -C apps/playground dev",
15
- "search": "node scripts/rg-fast.mjs",
16
- "rg:fast": "node scripts/rg-fast.mjs",
17
- "build": "tsc -p tsconfig.json",
18
- "lint": "eslint \"src/**/*.ts\" --max-warnings=0",
19
- "lint:fix": "eslint \"src/**/*.ts\" --fix",
20
- "dev": "tsc -w -p tsconfig.json",
21
- "test": "vitest run",
22
- "prepublishOnly": "npm run build",
23
- "release:patch": "npm version patch && git push --follow-tags",
24
- "release:minor": "npm version minor && git push --follow-tags",
25
- "release:major": "npm version major && git push --follow-tags"
26
- },
27
13
  "keywords": [
28
14
  "linkedin",
29
15
  "sales-navigator",
@@ -53,5 +39,18 @@
53
39
  "husky": "^9.0.11",
54
40
  "typescript": "^5.9.3",
55
41
  "vitest": "^1.6.0"
42
+ },
43
+ "scripts": {
44
+ "dev:playground": "pnpm -C apps/playground dev",
45
+ "search": "node scripts/rg-fast.mjs",
46
+ "rg:fast": "node scripts/rg-fast.mjs",
47
+ "build": "tsc -p tsconfig.json",
48
+ "lint": "eslint \"src/**/*.ts\" --max-warnings=0",
49
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
50
+ "dev": "tsc -w -p tsconfig.json",
51
+ "test": "vitest run",
52
+ "release:patch": "npm version patch && git push --follow-tags",
53
+ "release:minor": "npm version minor && git push --follow-tags",
54
+ "release:major": "npm version major && git push --follow-tags"
56
55
  }
57
- }
56
+ }