linkedin-secret-sauce 0.3.29 → 0.4.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.
- package/dist/cookie-pool.d.ts +1 -1
- package/dist/cookie-pool.js +67 -35
- package/dist/cosiall-client.d.ts +20 -1
- package/dist/cosiall-client.js +48 -25
- package/dist/http-client.d.ts +1 -1
- package/dist/http-client.js +146 -63
- package/dist/linkedin-api.d.ts +97 -4
- package/dist/linkedin-api.js +416 -134
- package/dist/parsers/company-parser.d.ts +15 -1
- package/dist/parsers/company-parser.js +45 -17
- package/dist/parsers/profile-parser.d.ts +19 -1
- package/dist/parsers/profile-parser.js +131 -81
- package/dist/parsers/search-parser.d.ts +1 -1
- package/dist/parsers/search-parser.js +24 -11
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +28 -18
- package/dist/utils/search-encoder.d.ts +32 -1
- package/dist/utils/search-encoder.js +102 -58
- package/dist/utils/sentry.d.ts +1 -1
- package/dist/utils/sentry.js +56 -8
- package/package.json +1 -1
package/dist/http-client.js
CHANGED
|
@@ -45,11 +45,30 @@ function sleep(ms) {
|
|
|
45
45
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
46
|
}
|
|
47
47
|
function isRetryableStatus(status) {
|
|
48
|
-
return status === 429 ||
|
|
48
|
+
return (status === 429 ||
|
|
49
|
+
status === 500 ||
|
|
50
|
+
status === 502 ||
|
|
51
|
+
status === 503 ||
|
|
52
|
+
status === 504);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Redact sensitive parts of URL for safe logging.
|
|
56
|
+
* Preserves hostname and path but removes query parameters which may contain session info.
|
|
57
|
+
*/
|
|
58
|
+
function redactUrlForLogging(url) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = new URL(url);
|
|
61
|
+
// Keep only host and pathname, remove query params
|
|
62
|
+
return `${parsed.host}${parsed.pathname}`;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// If URL parsing fails, just return a safe placeholder
|
|
66
|
+
return "[invalid-url]";
|
|
67
|
+
}
|
|
49
68
|
}
|
|
50
69
|
async function executeLinkedInRequest(options, _operationName) {
|
|
51
70
|
const config = (0, config_1.getConfig)();
|
|
52
|
-
const op = _operationName ||
|
|
71
|
+
const op = _operationName || "request";
|
|
53
72
|
const perAccountAttempts = (config.maxRetries ?? 0) + 1;
|
|
54
73
|
const delay = config.retryDelayMs ?? 700;
|
|
55
74
|
// Setup proxy env if configured
|
|
@@ -62,7 +81,7 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
62
81
|
process.env.HTTP_PROXY = proxyUrl;
|
|
63
82
|
process.env.HTTPS_PROXY = proxyUrl;
|
|
64
83
|
proxySet = true;
|
|
65
|
-
(0, logger_1.log)(
|
|
84
|
+
(0, logger_1.log)("info", "proxy.set", { host: masked });
|
|
66
85
|
}
|
|
67
86
|
// Phase 1.4: Try ALL available accounts (not just 3)
|
|
68
87
|
const health = (0, cookie_pool_1.getCookiePoolHealth)();
|
|
@@ -77,8 +96,10 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
77
96
|
catch (err) {
|
|
78
97
|
const e = err;
|
|
79
98
|
// Phase 1.3: If NO_VALID_ACCOUNTS, try force refresh once
|
|
80
|
-
if (e?.code ===
|
|
81
|
-
(0, logger_1.log)(
|
|
99
|
+
if (e?.code === "NO_VALID_ACCOUNTS" && accountsTriedCount > 0) {
|
|
100
|
+
(0, logger_1.log)("warn", "http.noValidAccounts.forceRefresh", {
|
|
101
|
+
accountsTriedCount,
|
|
102
|
+
});
|
|
82
103
|
try {
|
|
83
104
|
await (0, cookie_pool_1.forceRefreshCookies)();
|
|
84
105
|
// Retry selection after refresh
|
|
@@ -86,31 +107,43 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
86
107
|
}
|
|
87
108
|
catch (refreshErr) {
|
|
88
109
|
const re = refreshErr;
|
|
89
|
-
(0, logger_1.log)(
|
|
110
|
+
(0, logger_1.log)("error", "http.forceRefreshFailed", { error: re?.message });
|
|
90
111
|
// Report to Sentry
|
|
91
112
|
try {
|
|
92
|
-
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require(
|
|
93
|
-
reportCriticalError(
|
|
113
|
+
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
114
|
+
reportCriticalError("All accounts exhausted, force refresh failed", {
|
|
94
115
|
error: re?.message,
|
|
95
116
|
accountsTriedCount,
|
|
96
|
-
tags: { component:
|
|
117
|
+
tags: { component: "http-client", severity: "critical" },
|
|
97
118
|
});
|
|
98
119
|
}
|
|
99
120
|
catch { }
|
|
100
121
|
}
|
|
101
122
|
}
|
|
102
|
-
if (!selection && process.env.NODE_ENV !==
|
|
123
|
+
if (!selection && process.env.NODE_ENV !== "test")
|
|
103
124
|
throw err;
|
|
104
125
|
}
|
|
105
126
|
if (!selection || !selection.accountId) {
|
|
106
|
-
if (process.env.NODE_ENV ===
|
|
127
|
+
if (process.env.NODE_ENV === "test") {
|
|
107
128
|
const fallback = rotation === 0
|
|
108
|
-
? {
|
|
109
|
-
|
|
129
|
+
? {
|
|
130
|
+
accountId: "acc1",
|
|
131
|
+
cookies: [
|
|
132
|
+
{ name: "li_at", value: "A" },
|
|
133
|
+
{ name: "JSESSIONID", value: '"csrf1"' },
|
|
134
|
+
],
|
|
135
|
+
}
|
|
136
|
+
: {
|
|
137
|
+
accountId: "acc2",
|
|
138
|
+
cookies: [
|
|
139
|
+
{ name: "li_at", value: "B" },
|
|
140
|
+
{ name: "JSESSIONID", value: '"csrf2"' },
|
|
141
|
+
],
|
|
142
|
+
};
|
|
110
143
|
selection = fallback;
|
|
111
144
|
}
|
|
112
145
|
else {
|
|
113
|
-
throw new errors_1.LinkedInClientError(
|
|
146
|
+
throw new errors_1.LinkedInClientError("No LinkedIn accounts available", "NO_ACCOUNTS", 503);
|
|
114
147
|
}
|
|
115
148
|
}
|
|
116
149
|
const { accountId, cookies } = selection;
|
|
@@ -118,51 +151,79 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
118
151
|
const csrf = (0, cookie_pool_1.extractCsrfToken)(cookies);
|
|
119
152
|
const cookieHeader = (0, cookie_pool_1.buildCookieHeader)(cookies);
|
|
120
153
|
// Choose header profile based on Sales vs Voyager context
|
|
121
|
-
const isSales = /\/sales\//i.test(String(options.headers?.Referer ||
|
|
122
|
-
|
|
154
|
+
const isSales = /\/sales\//i.test(String(options.headers?.Referer || "")) ||
|
|
155
|
+
/https:\/\/www\.linkedin\.com\/sales-api/i.test(options.url);
|
|
123
156
|
const baseHeaders = isSales
|
|
124
|
-
? (0, linkedin_config_1.buildSalesHeaders)({
|
|
125
|
-
|
|
157
|
+
? (0, linkedin_config_1.buildSalesHeaders)({
|
|
158
|
+
cookiesHeader: cookieHeader,
|
|
159
|
+
csrf,
|
|
160
|
+
referer: options.headers?.Referer,
|
|
161
|
+
})
|
|
162
|
+
: (0, linkedin_config_1.buildVoyagerHeaders)({
|
|
163
|
+
cookiesHeader: cookieHeader,
|
|
164
|
+
csrf,
|
|
165
|
+
referer: options.headers?.Referer,
|
|
166
|
+
});
|
|
126
167
|
const headers = {
|
|
127
168
|
...baseHeaders,
|
|
128
169
|
...(options.headers || {}),
|
|
129
170
|
};
|
|
130
171
|
for (let attempt = 0; attempt < perAccountAttempts; attempt++) {
|
|
131
172
|
const started = Date.now();
|
|
132
|
-
(0, logger_1.log)(
|
|
173
|
+
(0, logger_1.log)("debug", "http.attempt", {
|
|
174
|
+
accountId,
|
|
175
|
+
attempt: attempt + 1,
|
|
176
|
+
method: options.method ?? "GET",
|
|
177
|
+
url: redactUrlForLogging(options.url),
|
|
178
|
+
});
|
|
133
179
|
let res;
|
|
134
180
|
try {
|
|
135
181
|
const init = {
|
|
136
|
-
method: options.method ??
|
|
182
|
+
method: options.method ?? "GET",
|
|
137
183
|
headers,
|
|
138
184
|
};
|
|
139
185
|
if (options.body !== undefined) {
|
|
140
|
-
init.body =
|
|
186
|
+
init.body =
|
|
187
|
+
typeof options.body === "string"
|
|
188
|
+
? options.body
|
|
189
|
+
: JSON.stringify(options.body);
|
|
141
190
|
}
|
|
142
191
|
res = (await fetch(options.url, init));
|
|
143
192
|
}
|
|
144
193
|
catch (err) {
|
|
145
194
|
const e = err;
|
|
146
|
-
const code = e?.code || e?.cause?.code ||
|
|
147
|
-
(0, logger_1.log)(
|
|
148
|
-
|
|
195
|
+
const code = e?.code || e?.cause?.code || "FETCH_FAILED";
|
|
196
|
+
(0, logger_1.log)("error", "http.networkError", {
|
|
197
|
+
accountId,
|
|
198
|
+
url: redactUrlForLogging(options.url),
|
|
199
|
+
code,
|
|
200
|
+
message: String(e?.message || err),
|
|
201
|
+
});
|
|
202
|
+
(0, metrics_1.incrementMetric)("httpFailures");
|
|
149
203
|
try {
|
|
150
|
-
(0, request_history_1.recordRequest)({
|
|
204
|
+
(0, request_history_1.recordRequest)({
|
|
205
|
+
operation: op,
|
|
206
|
+
selector: options.url,
|
|
207
|
+
status: 0,
|
|
208
|
+
durationMs: Date.now() - started,
|
|
209
|
+
accountId,
|
|
210
|
+
errorMessage: String(e?.message || err),
|
|
211
|
+
});
|
|
151
212
|
}
|
|
152
213
|
catch { }
|
|
153
214
|
// Report network errors to Sentry
|
|
154
215
|
try {
|
|
155
|
-
const { reportWarningToSentry } = await Promise.resolve().then(() => __importStar(require(
|
|
156
|
-
reportWarningToSentry(
|
|
216
|
+
const { reportWarningToSentry } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
217
|
+
reportWarningToSentry("Network error during LinkedIn request", {
|
|
157
218
|
code,
|
|
158
|
-
url: options.url,
|
|
219
|
+
url: redactUrlForLogging(options.url),
|
|
159
220
|
accountId,
|
|
160
221
|
error: String(e?.message || err),
|
|
161
|
-
tags: { component:
|
|
222
|
+
tags: { component: "http-client", severity: "high" },
|
|
162
223
|
});
|
|
163
224
|
}
|
|
164
225
|
catch { }
|
|
165
|
-
lastError = new errors_1.LinkedInClientError(
|
|
226
|
+
lastError = new errors_1.LinkedInClientError("LinkedIn fetch failed", "REQUEST_FAILED", 0, accountId);
|
|
166
227
|
break; // rotate to next account
|
|
167
228
|
}
|
|
168
229
|
if (res.ok) {
|
|
@@ -171,8 +232,11 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
171
232
|
(0, cookie_pool_1.reportAccountSuccess)(accountId);
|
|
172
233
|
}
|
|
173
234
|
catch { }
|
|
174
|
-
(0, logger_1.log)(
|
|
175
|
-
|
|
235
|
+
(0, logger_1.log)("info", "http.success", {
|
|
236
|
+
accountId,
|
|
237
|
+
url: redactUrlForLogging(options.url),
|
|
238
|
+
});
|
|
239
|
+
(0, metrics_1.incrementMetric)("httpSuccess");
|
|
176
240
|
try {
|
|
177
241
|
(0, request_history_1.recordRequest)({
|
|
178
242
|
operation: op,
|
|
@@ -192,10 +256,10 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
192
256
|
(0, cookie_pool_1.reportAccountFailure)(accountId, true);
|
|
193
257
|
}
|
|
194
258
|
catch { }
|
|
195
|
-
(0, logger_1.log)(
|
|
196
|
-
(0, metrics_1.incrementMetric)(
|
|
197
|
-
(0, metrics_1.incrementMetric)(
|
|
198
|
-
lastError = new errors_1.LinkedInClientError(`LinkedIn request failed: ${status}`,
|
|
259
|
+
(0, logger_1.log)("warn", "http.rotateOnAuth", { accountId, status });
|
|
260
|
+
(0, metrics_1.incrementMetric)("authErrors");
|
|
261
|
+
(0, metrics_1.incrementMetric)("httpFailures");
|
|
262
|
+
lastError = new errors_1.LinkedInClientError(`LinkedIn request failed: ${status}`, "REQUEST_FAILED", status, accountId);
|
|
199
263
|
try {
|
|
200
264
|
(0, request_history_1.recordRequest)({
|
|
201
265
|
operation: op,
|
|
@@ -203,7 +267,7 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
203
267
|
status,
|
|
204
268
|
durationMs: Date.now() - started,
|
|
205
269
|
accountId,
|
|
206
|
-
errorMessage:
|
|
270
|
+
errorMessage: "rotate",
|
|
207
271
|
});
|
|
208
272
|
}
|
|
209
273
|
catch { }
|
|
@@ -212,38 +276,54 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
212
276
|
// Phase 1.5: Proxy fallback on 502/503/504 if enabled
|
|
213
277
|
const isProxyError = status === 502 || status === 503 || status === 504;
|
|
214
278
|
const fallbackEnabled = config.fallbackWithoutProxyOnError ?? false;
|
|
215
|
-
if (isProxyError &&
|
|
216
|
-
|
|
279
|
+
if (isProxyError &&
|
|
280
|
+
fallbackEnabled &&
|
|
281
|
+
proxySet &&
|
|
282
|
+
attempt < perAccountAttempts - 1) {
|
|
283
|
+
(0, logger_1.log)("warn", "http.proxyFallback", {
|
|
284
|
+
accountId,
|
|
285
|
+
status,
|
|
286
|
+
attempt: attempt + 1,
|
|
287
|
+
});
|
|
217
288
|
// Temporarily disable proxy for this retry
|
|
218
289
|
delete process.env.HTTP_PROXY;
|
|
219
290
|
delete process.env.HTTPS_PROXY;
|
|
220
291
|
proxySet = false;
|
|
221
292
|
// Report to Sentry
|
|
222
293
|
try {
|
|
223
|
-
const { reportWarningToSentry } = await Promise.resolve().then(() => __importStar(require(
|
|
224
|
-
reportWarningToSentry(
|
|
294
|
+
const { reportWarningToSentry } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
295
|
+
reportWarningToSentry("Proxy error, retrying without proxy", {
|
|
225
296
|
status,
|
|
226
|
-
url: options.url,
|
|
297
|
+
url: redactUrlForLogging(options.url),
|
|
227
298
|
accountId,
|
|
228
|
-
tags: { component:
|
|
299
|
+
tags: { component: "http-client", severity: "medium" },
|
|
229
300
|
});
|
|
230
301
|
}
|
|
231
302
|
catch { }
|
|
232
|
-
(0, metrics_1.incrementMetric)(
|
|
303
|
+
(0, metrics_1.incrementMetric)("httpRetries");
|
|
233
304
|
await sleep(delay);
|
|
234
305
|
continue;
|
|
235
306
|
}
|
|
236
307
|
// Retryable 5xx on same account
|
|
237
|
-
if (
|
|
238
|
-
(0, logger_1.log)(
|
|
239
|
-
|
|
308
|
+
if (status >= 500 && status < 600 && attempt < perAccountAttempts - 1) {
|
|
309
|
+
(0, logger_1.log)("debug", "http.retry", {
|
|
310
|
+
accountId,
|
|
311
|
+
status,
|
|
312
|
+
attempt: attempt + 1,
|
|
313
|
+
nextDelayMs: delay,
|
|
314
|
+
});
|
|
315
|
+
(0, metrics_1.incrementMetric)("httpRetries");
|
|
240
316
|
await sleep(delay);
|
|
241
317
|
continue;
|
|
242
318
|
}
|
|
243
319
|
// Non-retryable: throw
|
|
244
|
-
const err = new errors_1.LinkedInClientError(`LinkedIn request failed: ${status}`,
|
|
245
|
-
(0, metrics_1.incrementMetric)(
|
|
246
|
-
(0, logger_1.log)(status >= 500 ?
|
|
320
|
+
const err = new errors_1.LinkedInClientError(`LinkedIn request failed: ${status}`, "REQUEST_FAILED", status, accountId);
|
|
321
|
+
(0, metrics_1.incrementMetric)("httpFailures");
|
|
322
|
+
(0, logger_1.log)(status >= 500 ? "warn" : "error", "http.fail", {
|
|
323
|
+
accountId,
|
|
324
|
+
status,
|
|
325
|
+
url: redactUrlForLogging(options.url),
|
|
326
|
+
});
|
|
247
327
|
try {
|
|
248
328
|
(0, request_history_1.recordRequest)({
|
|
249
329
|
operation: op,
|
|
@@ -260,21 +340,24 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
260
340
|
}
|
|
261
341
|
// Exhausted all accounts
|
|
262
342
|
const errMsg = `All ${accountsTriedCount} LinkedIn accounts exhausted`;
|
|
263
|
-
(0, logger_1.log)(
|
|
343
|
+
(0, logger_1.log)("error", "http.allAccountsExhausted", {
|
|
344
|
+
accountsTriedCount,
|
|
345
|
+
maxRotations,
|
|
346
|
+
});
|
|
264
347
|
// Report critical failure to Sentry
|
|
265
348
|
try {
|
|
266
|
-
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require(
|
|
349
|
+
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
267
350
|
reportCriticalError(errMsg, {
|
|
268
351
|
accountsTriedCount,
|
|
269
352
|
maxRotations,
|
|
270
353
|
lastError: lastError?.message,
|
|
271
|
-
tags: { component:
|
|
354
|
+
tags: { component: "http-client", severity: "critical" },
|
|
272
355
|
});
|
|
273
356
|
}
|
|
274
357
|
catch { }
|
|
275
358
|
if (lastError)
|
|
276
359
|
throw lastError;
|
|
277
|
-
throw new errors_1.LinkedInClientError(errMsg,
|
|
360
|
+
throw new errors_1.LinkedInClientError(errMsg, "ALL_ACCOUNTS_FAILED", 503);
|
|
278
361
|
}
|
|
279
362
|
finally {
|
|
280
363
|
// Restore proxy env
|
|
@@ -287,7 +370,7 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
287
370
|
delete process.env.HTTPS_PROXY;
|
|
288
371
|
else
|
|
289
372
|
process.env.HTTPS_PROXY = prevHttps;
|
|
290
|
-
(0, logger_1.log)(
|
|
373
|
+
(0, logger_1.log)("info", "proxy.restore", {});
|
|
291
374
|
}
|
|
292
375
|
}
|
|
293
376
|
}
|
|
@@ -300,13 +383,13 @@ function parseProxyString(input) {
|
|
|
300
383
|
if (/^\w+:\/\//.test(s)) {
|
|
301
384
|
try {
|
|
302
385
|
const u = new URL(s);
|
|
303
|
-
if (u.protocol !==
|
|
304
|
-
throw new errors_1.LinkedInClientError(`Unsupported proxy protocol: ${u.protocol}`,
|
|
386
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
387
|
+
throw new errors_1.LinkedInClientError(`Unsupported proxy protocol: ${u.protocol}`, "INVALID_CONFIG", 400);
|
|
305
388
|
}
|
|
306
|
-
const user = u.username ||
|
|
307
|
-
const pass = u.password ||
|
|
389
|
+
const user = u.username || "";
|
|
390
|
+
const pass = u.password || "";
|
|
308
391
|
const hostport = u.host; // includes brackets for IPv6
|
|
309
|
-
const url = `${u.protocol}//${user ? `${user}:${pass}@` :
|
|
392
|
+
const url = `${u.protocol}//${user ? `${user}:${pass}@` : ""}${hostport}`;
|
|
310
393
|
const masked = user ? `${hostport}:${user}:***` : hostport;
|
|
311
394
|
return { url, masked };
|
|
312
395
|
}
|
|
@@ -323,15 +406,15 @@ function parseProxyString(input) {
|
|
|
323
406
|
const user = m[3];
|
|
324
407
|
const pass = m[4];
|
|
325
408
|
// Ensure brackets for IPv6 in URL
|
|
326
|
-
const needsBrackets = host.includes(
|
|
409
|
+
const needsBrackets = host.includes(":") && !/^\[.*\]$/.test(host);
|
|
327
410
|
if (needsBrackets)
|
|
328
411
|
host = `[${host}]`;
|
|
329
|
-
const auth = user && pass ? `${user}:${pass}@` :
|
|
412
|
+
const auth = user && pass ? `${user}:${pass}@` : "";
|
|
330
413
|
const url = `http://${auth}${host}:${port}`;
|
|
331
414
|
const hostport = `${host}:${port}`;
|
|
332
415
|
const masked = user && pass ? `${hostport}:${user}:***` : hostport;
|
|
333
416
|
return { url, masked };
|
|
334
417
|
}
|
|
335
418
|
// Fallback: conservative
|
|
336
|
-
return { url: s.includes(
|
|
419
|
+
return { url: s.includes("://") ? s : `http://${s}`, masked: "***" };
|
|
337
420
|
}
|
package/dist/linkedin-api.d.ts
CHANGED
|
@@ -1,7 +1,79 @@
|
|
|
1
|
-
import type { SalesSearchFilters } from
|
|
2
|
-
import type { LinkedInProfile,
|
|
1
|
+
import type { SalesSearchFilters } from "./types";
|
|
2
|
+
import type { LinkedInProfile, SearchSalesResult, TypeaheadResult, SalesNavigatorProfile, Company } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Fetches a LinkedIn profile by vanity URL (public identifier).
|
|
5
|
+
* Results are cached for the configured TTL (default: 15 minutes).
|
|
6
|
+
*
|
|
7
|
+
* @param vanity - The public identifier from LinkedIn URL (e.g., "johndoe" from linkedin.com/in/johndoe)
|
|
8
|
+
* @returns Parsed LinkedInProfile with positions, education, skills, and metadata
|
|
9
|
+
* @throws LinkedInClientError with code NOT_FOUND if profile doesn't exist
|
|
10
|
+
* @throws LinkedInClientError with code PARSE_ERROR if response cannot be parsed
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const profile = await getProfileByVanity('johndoe');
|
|
15
|
+
* console.log(profile.firstName, profile.headline);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
3
18
|
export declare function getProfileByVanity(vanity: string): Promise<LinkedInProfile>;
|
|
19
|
+
/**
|
|
20
|
+
* Fetches a LinkedIn profile by FSD key or URN.
|
|
21
|
+
* Accepts multiple input formats and normalizes them.
|
|
22
|
+
* Results are cached for the configured TTL (default: 15 minutes).
|
|
23
|
+
*
|
|
24
|
+
* @param fsdKey - Profile identifier in any of these formats:
|
|
25
|
+
* - Bare key: "ABC123xyz"
|
|
26
|
+
* - FSD profile URN: "urn:li:fsd_profile:ABC123xyz"
|
|
27
|
+
* - Sales profile URN: "urn:li:fs_salesProfile:(ABC123xyz,NAME_SEARCH,abc)"
|
|
28
|
+
* @returns Parsed LinkedInProfile with positions, education, skills, and metadata
|
|
29
|
+
* @throws LinkedInClientError with code INVALID_INPUT if URN format is invalid
|
|
30
|
+
* @throws LinkedInClientError with code NOT_FOUND if profile doesn't exist
|
|
31
|
+
* @throws LinkedInClientError with code PARSE_ERROR if response cannot be parsed
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // By bare key
|
|
36
|
+
* const profile = await getProfileByUrn('ABC123xyz');
|
|
37
|
+
*
|
|
38
|
+
* // By URN from search results
|
|
39
|
+
* const profile = await getProfileByUrn(searchResult.salesProfileUrn);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
4
42
|
export declare function getProfileByUrn(fsdKey: string): Promise<LinkedInProfile>;
|
|
43
|
+
/**
|
|
44
|
+
* Searches Sales Navigator for leads matching the given criteria.
|
|
45
|
+
* Supports pagination, advanced filters, and session-based account stickiness.
|
|
46
|
+
*
|
|
47
|
+
* @param keywords - Search keywords (max 2000 characters)
|
|
48
|
+
* @param options - Search configuration options
|
|
49
|
+
* @param options.start - Pagination offset (default: 0)
|
|
50
|
+
* @param options.count - Results per page (default: 25)
|
|
51
|
+
* @param options.decorationId - LinkedIn decoration ID for response format
|
|
52
|
+
* @param options.filters - Advanced search filters (title, company, geography, etc.)
|
|
53
|
+
* @param options.rawQuery - Raw query string (bypasses filter encoding)
|
|
54
|
+
* @param options.sessionId - Session ID for consistent pagination (same account across pages)
|
|
55
|
+
* @returns SearchSalesResult with items array, pagination info, and session metadata
|
|
56
|
+
* @throws LinkedInClientError with code INVALID_INPUT if keywords exceed max length
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // Simple search
|
|
61
|
+
* const results = await searchSalesLeads('CEO');
|
|
62
|
+
*
|
|
63
|
+
* // Paginated search with session persistence
|
|
64
|
+
* const page1 = await searchSalesLeads('CEO', { start: 0, count: 25 });
|
|
65
|
+
* const sessionId = page1._meta?.sessionId;
|
|
66
|
+
* const page2 = await searchSalesLeads('CEO', { start: 25, count: 25, sessionId });
|
|
67
|
+
*
|
|
68
|
+
* // With filters
|
|
69
|
+
* const filtered = await searchSalesLeads('', {
|
|
70
|
+
* filters: {
|
|
71
|
+
* role: { seniority_ids: [8, 9, 10] }, // VP, CXO, Partner
|
|
72
|
+
* company: { headcount: { include: ['501-1000', '1001-5000'] } }
|
|
73
|
+
* }
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
5
77
|
export declare function searchSalesLeads(keywords: string, options?: {
|
|
6
78
|
start?: number;
|
|
7
79
|
count?: number;
|
|
@@ -9,20 +81,41 @@ export declare function searchSalesLeads(keywords: string, options?: {
|
|
|
9
81
|
filters?: SalesSearchFilters;
|
|
10
82
|
rawQuery?: string;
|
|
11
83
|
sessionId?: string;
|
|
12
|
-
}): Promise<SearchSalesResult
|
|
84
|
+
}): Promise<SearchSalesResult>;
|
|
13
85
|
export declare function getProfilesBatch(vanities: string[], concurrency?: number): Promise<(LinkedInProfile | null)[]>;
|
|
14
86
|
export declare function resolveCompanyUniversalName(universalName: string): Promise<{
|
|
15
87
|
companyId?: string;
|
|
16
88
|
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Fetches a company by its numeric LinkedIn ID.
|
|
91
|
+
* Results are cached for the configured TTL (default: 10 minutes).
|
|
92
|
+
*
|
|
93
|
+
* @param companyId - Numeric company ID (e.g., "1234567")
|
|
94
|
+
* @returns Company object with name, description, size, headquarters, etc.
|
|
95
|
+
* @throws LinkedInClientError with code NOT_FOUND if company doesn't exist
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const company = await getCompanyById('1234567');
|
|
100
|
+
* console.log(company.name, company.sizeLabel);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
17
103
|
export declare function getCompanyById(companyId: string): Promise<Company>;
|
|
18
104
|
export declare function getCompanyByUrl(companyUrl: string): Promise<Company>;
|
|
105
|
+
/**
|
|
106
|
+
* Fetch multiple companies in parallel with controlled concurrency.
|
|
107
|
+
* @param companyIds - Array of company IDs (numeric strings)
|
|
108
|
+
* @param concurrency - Maximum parallel requests (default: 4, max: 16)
|
|
109
|
+
* @returns Array of Company objects (null for failed fetches, preserves order)
|
|
110
|
+
*/
|
|
111
|
+
export declare function getCompaniesBatch(companyIds: string[], concurrency?: number): Promise<(Company | null)[]>;
|
|
19
112
|
export declare function typeahead(options: {
|
|
20
113
|
type: string;
|
|
21
114
|
query?: string;
|
|
22
115
|
start?: number;
|
|
23
116
|
count?: number;
|
|
24
117
|
}): Promise<TypeaheadResult>;
|
|
25
|
-
export { YEARS_OPTIONS as YEARS_AT_COMPANY_OPTIONS, YEARS_OPTIONS as YEARS_IN_POSITION_OPTIONS, YEARS_OPTIONS as YEARS_OF_EXPERIENCE_OPTIONS, SENIORITY_OPTIONS, FUNCTION_OPTIONS, REGION_OPTIONS, LANGUAGE_OPTIONS, INDUSTRY_OPTIONS, COMPANY_SIZE_OPTIONS, } from
|
|
118
|
+
export { YEARS_OPTIONS as YEARS_AT_COMPANY_OPTIONS, YEARS_OPTIONS as YEARS_IN_POSITION_OPTIONS, YEARS_OPTIONS as YEARS_OF_EXPERIENCE_OPTIONS, SENIORITY_OPTIONS, FUNCTION_OPTIONS, REGION_OPTIONS, LANGUAGE_OPTIONS, INDUSTRY_OPTIONS, COMPANY_SIZE_OPTIONS, } from "./constants";
|
|
26
119
|
/**
|
|
27
120
|
* Returns static years at company options (no API call needed)
|
|
28
121
|
*/
|