linkedin-secret-sauce 0.1.1 → 0.2.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/README.md +193 -60
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -2
- package/dist/cookie-pool.d.ts +11 -0
- package/dist/cookie-pool.js +57 -4
- package/dist/cosiall-client.js +20 -5
- package/dist/http-client.d.ts +1 -1
- package/dist/http-client.js +120 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/linkedin-api.d.ts +22 -3
- package/dist/linkedin-api.js +380 -32
- package/dist/parsers/company-parser.d.ts +2 -0
- package/dist/parsers/company-parser.js +30 -0
- package/dist/parsers/image-parser.d.ts +10 -0
- package/dist/parsers/image-parser.js +16 -0
- package/dist/parsers/profile-parser.d.ts +1 -1
- package/dist/parsers/profile-parser.js +70 -25
- package/dist/parsers/search-parser.d.ts +1 -1
- package/dist/parsers/search-parser.js +14 -4
- package/dist/types.d.ts +147 -0
- package/dist/utils/errors.d.ts +4 -0
- package/dist/utils/errors.js +5 -0
- package/dist/utils/linkedin-config.d.ts +43 -0
- package/dist/utils/linkedin-config.js +77 -0
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/metrics.d.ts +9 -1
- package/dist/utils/metrics.js +16 -2
- package/dist/utils/request-history.d.ts +12 -0
- package/dist/utils/request-history.js +45 -0
- package/dist/utils/search-encoder.d.ts +4 -0
- package/dist/utils/search-encoder.js +164 -0
- package/package.json +59 -44
package/dist/http-client.js
CHANGED
|
@@ -6,6 +6,8 @@ const cookie_pool_1 = require("./cookie-pool");
|
|
|
6
6
|
const errors_1 = require("./utils/errors");
|
|
7
7
|
const logger_1 = require("./utils/logger");
|
|
8
8
|
const metrics_1 = require("./utils/metrics");
|
|
9
|
+
const request_history_1 = require("./utils/request-history");
|
|
10
|
+
const linkedin_config_1 = require("./utils/linkedin-config");
|
|
9
11
|
function sleep(ms) {
|
|
10
12
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
13
|
}
|
|
@@ -14,6 +16,7 @@ function isRetryableStatus(status) {
|
|
|
14
16
|
}
|
|
15
17
|
async function executeLinkedInRequest(options, _operationName) {
|
|
16
18
|
const config = (0, config_1.getConfig)();
|
|
19
|
+
const op = _operationName || 'request';
|
|
17
20
|
const perAccountAttempts = (config.maxRetries ?? 0) + 1;
|
|
18
21
|
const delay = config.retryDelayMs ?? 700;
|
|
19
22
|
// Setup proxy env if configured
|
|
@@ -22,13 +25,11 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
22
25
|
let proxySet = false;
|
|
23
26
|
try {
|
|
24
27
|
if (config.proxyString) {
|
|
25
|
-
const
|
|
26
|
-
const proxyUrl = user && pass
|
|
27
|
-
? `http://${user}:${pass}@${host}:${port}`
|
|
28
|
-
: `http://${host}:${port}`;
|
|
28
|
+
const { url: proxyUrl, masked } = parseProxyString(config.proxyString);
|
|
29
29
|
process.env.HTTP_PROXY = proxyUrl;
|
|
30
30
|
process.env.HTTPS_PROXY = proxyUrl;
|
|
31
31
|
proxySet = true;
|
|
32
|
+
(0, logger_1.log)('info', 'proxy.set', { host: masked });
|
|
32
33
|
}
|
|
33
34
|
// Try up to 3 different accounts on auth/rate failures
|
|
34
35
|
let lastError;
|
|
@@ -55,18 +56,42 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
55
56
|
const { accountId, cookies } = selection;
|
|
56
57
|
const csrf = (0, cookie_pool_1.extractCsrfToken)(cookies);
|
|
57
58
|
const cookieHeader = (0, cookie_pool_1.buildCookieHeader)(cookies);
|
|
59
|
+
// Choose header profile based on Sales vs Voyager context
|
|
60
|
+
const isSales = /\/sales\//i.test(String(options.headers?.Referer || ''))
|
|
61
|
+
|| /https:\/\/www\.linkedin\.com\/sales-api/i.test(options.url);
|
|
62
|
+
const baseHeaders = isSales
|
|
63
|
+
? (0, linkedin_config_1.buildSalesHeaders)({ cookiesHeader: cookieHeader, csrf, referer: options.headers?.Referer })
|
|
64
|
+
: (0, linkedin_config_1.buildVoyagerHeaders)({ cookiesHeader: cookieHeader, csrf, referer: options.headers?.Referer });
|
|
58
65
|
const headers = {
|
|
59
|
-
|
|
60
|
-
'csrf-token': csrf,
|
|
66
|
+
...baseHeaders,
|
|
61
67
|
...(options.headers || {}),
|
|
62
68
|
};
|
|
63
69
|
for (let attempt = 0; attempt < perAccountAttempts; attempt++) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const started = Date.now();
|
|
71
|
+
(0, logger_1.log)('debug', 'http.attempt', { accountId, attempt: attempt + 1, method: options.method ?? 'GET', url: options.url });
|
|
72
|
+
let res;
|
|
73
|
+
try {
|
|
74
|
+
const init = {
|
|
75
|
+
method: options.method ?? 'GET',
|
|
76
|
+
headers,
|
|
77
|
+
};
|
|
78
|
+
if (options.body !== undefined) {
|
|
79
|
+
init.body = typeof options.body === 'string' ? options.body : JSON.stringify(options.body);
|
|
80
|
+
}
|
|
81
|
+
res = (await fetch(options.url, init));
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const e = err;
|
|
85
|
+
const code = e?.code || e?.cause?.code || 'FETCH_FAILED';
|
|
86
|
+
(0, logger_1.log)('error', 'http.networkError', { accountId, url: options.url, code, message: String(e?.message || err) });
|
|
87
|
+
(0, metrics_1.incrementMetric)('httpFailures');
|
|
88
|
+
try {
|
|
89
|
+
(0, request_history_1.recordRequest)({ operation: op, selector: options.url, status: 0, durationMs: Date.now() - started, accountId, errorMessage: String(e?.message || err) });
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
lastError = new errors_1.LinkedInClientError('LinkedIn fetch failed', 'REQUEST_FAILED', 0, accountId);
|
|
93
|
+
break; // rotate to next account
|
|
94
|
+
}
|
|
70
95
|
if (res.ok) {
|
|
71
96
|
// success
|
|
72
97
|
try {
|
|
@@ -75,6 +100,16 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
75
100
|
catch { }
|
|
76
101
|
(0, logger_1.log)('info', 'http.success', { accountId, url: options.url });
|
|
77
102
|
(0, metrics_1.incrementMetric)('httpSuccess');
|
|
103
|
+
try {
|
|
104
|
+
(0, request_history_1.recordRequest)({
|
|
105
|
+
operation: op,
|
|
106
|
+
selector: options.url,
|
|
107
|
+
status: res.status ?? 200,
|
|
108
|
+
durationMs: Date.now() - started,
|
|
109
|
+
accountId,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
78
113
|
return (await res.json());
|
|
79
114
|
}
|
|
80
115
|
const status = res.status ?? 0;
|
|
@@ -86,18 +121,44 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
86
121
|
catch { }
|
|
87
122
|
(0, logger_1.log)('warn', 'http.rotateOnAuth', { accountId, status });
|
|
88
123
|
(0, metrics_1.incrementMetric)('authErrors');
|
|
124
|
+
(0, metrics_1.incrementMetric)('httpFailures');
|
|
89
125
|
lastError = new errors_1.LinkedInClientError(`LinkedIn request failed: ${status}`, 'REQUEST_FAILED', status, accountId);
|
|
126
|
+
try {
|
|
127
|
+
(0, request_history_1.recordRequest)({
|
|
128
|
+
operation: op,
|
|
129
|
+
selector: options.url,
|
|
130
|
+
status,
|
|
131
|
+
durationMs: Date.now() - started,
|
|
132
|
+
accountId,
|
|
133
|
+
errorMessage: 'rotate',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch { }
|
|
90
137
|
break; // break inner loop to rotate account
|
|
91
138
|
}
|
|
92
139
|
// Retryable 5xx on same account
|
|
93
140
|
if ((status >= 500 && status < 600) && attempt < perAccountAttempts - 1) {
|
|
94
|
-
(0, logger_1.log)('debug', 'http.retry', { accountId, status, nextDelayMs: delay });
|
|
141
|
+
(0, logger_1.log)('debug', 'http.retry', { accountId, status, attempt: attempt + 1, nextDelayMs: delay });
|
|
95
142
|
(0, metrics_1.incrementMetric)('httpRetries');
|
|
96
143
|
await sleep(delay);
|
|
97
144
|
continue;
|
|
98
145
|
}
|
|
99
146
|
// Non-retryable: throw
|
|
100
|
-
|
|
147
|
+
const err = new errors_1.LinkedInClientError(`LinkedIn request failed: ${status}`, 'REQUEST_FAILED', status, accountId);
|
|
148
|
+
(0, metrics_1.incrementMetric)('httpFailures');
|
|
149
|
+
(0, logger_1.log)(status >= 500 ? 'warn' : 'error', 'http.fail', { accountId, status, url: options.url });
|
|
150
|
+
try {
|
|
151
|
+
(0, request_history_1.recordRequest)({
|
|
152
|
+
operation: op,
|
|
153
|
+
selector: options.url,
|
|
154
|
+
status,
|
|
155
|
+
durationMs: Date.now() - started,
|
|
156
|
+
accountId,
|
|
157
|
+
errorMessage: err.message,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
161
|
+
throw err;
|
|
101
162
|
}
|
|
102
163
|
}
|
|
103
164
|
// Exhausted rotations
|
|
@@ -116,6 +177,51 @@ async function executeLinkedInRequest(options, _operationName) {
|
|
|
116
177
|
delete process.env.HTTPS_PROXY;
|
|
117
178
|
else
|
|
118
179
|
process.env.HTTPS_PROXY = prevHttps;
|
|
180
|
+
(0, logger_1.log)('info', 'proxy.restore', {});
|
|
119
181
|
}
|
|
120
182
|
}
|
|
121
183
|
}
|
|
184
|
+
// Parse proxy string supporting:
|
|
185
|
+
// - Full URL forms: http(s)://[user[:pass]@]host:port (host may be [ipv6])
|
|
186
|
+
// - Colon-delimited: host:port[:user:pass] with optional [ipv6]
|
|
187
|
+
function parseProxyString(input) {
|
|
188
|
+
const s = String(input).trim();
|
|
189
|
+
// URL form
|
|
190
|
+
if (/^\w+:\/\//.test(s)) {
|
|
191
|
+
try {
|
|
192
|
+
const u = new URL(s);
|
|
193
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
|
|
194
|
+
throw new errors_1.LinkedInClientError(`Unsupported proxy protocol: ${u.protocol}`, 'INVALID_CONFIG', 400);
|
|
195
|
+
}
|
|
196
|
+
const user = u.username || '';
|
|
197
|
+
const pass = u.password || '';
|
|
198
|
+
const hostport = u.host; // includes brackets for IPv6
|
|
199
|
+
const url = `${u.protocol}//${user ? `${user}:${pass}@` : ''}${hostport}`;
|
|
200
|
+
const masked = user ? `${hostport}:${user}:***` : hostport;
|
|
201
|
+
return { url, masked };
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// fall through to colon parsing
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Colon-delimited
|
|
208
|
+
// [ipv6]:port[:user:pass] OR host:port[:user:pass]
|
|
209
|
+
const m = s.match(/^(\[?[^\]]+\]?):(\d+)(?::([^:]*):(.+))?$/);
|
|
210
|
+
if (m) {
|
|
211
|
+
let host = m[1];
|
|
212
|
+
const port = m[2];
|
|
213
|
+
const user = m[3];
|
|
214
|
+
const pass = m[4];
|
|
215
|
+
// Ensure brackets for IPv6 in URL
|
|
216
|
+
const needsBrackets = host.includes(':') && !/^\[.*\]$/.test(host);
|
|
217
|
+
if (needsBrackets)
|
|
218
|
+
host = `[${host}]`;
|
|
219
|
+
const auth = user && pass ? `${user}:${pass}@` : '';
|
|
220
|
+
const url = `http://${auth}${host}:${port}`;
|
|
221
|
+
const hostport = `${host}:${port}`;
|
|
222
|
+
const masked = user && pass ? `${hostport}:${user}:***` : hostport;
|
|
223
|
+
return { url, masked };
|
|
224
|
+
}
|
|
225
|
+
// Fallback: conservative
|
|
226
|
+
return { url: s.includes('://') ? s : `http://${s}`, masked: '***' };
|
|
227
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,5 @@ export { parseSalesSearchResults } from './parsers/search-parser';
|
|
|
8
8
|
export * from './linkedin-api';
|
|
9
9
|
export * from './types';
|
|
10
10
|
export * from './utils/metrics';
|
|
11
|
+
export { getRequestHistory, clearRequestHistory } from './utils/request-history';
|
|
12
|
+
export type { RequestHistoryEntry } from './utils/request-history';
|
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.parseSalesSearchResults = exports.parseFullProfile = exports.LinkedInClientError = exports.getConfig = exports.initializeLinkedInClient = void 0;
|
|
17
|
+
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; } });
|
|
@@ -29,3 +29,6 @@ Object.defineProperty(exports, "parseSalesSearchResults", { enumerable: true, ge
|
|
|
29
29
|
__exportStar(require("./linkedin-api"), exports);
|
|
30
30
|
__exportStar(require("./types"), exports);
|
|
31
31
|
__exportStar(require("./utils/metrics"), exports);
|
|
32
|
+
var request_history_1 = require("./utils/request-history");
|
|
33
|
+
Object.defineProperty(exports, "getRequestHistory", { enumerable: true, get: function () { return request_history_1.getRequestHistory; } });
|
|
34
|
+
Object.defineProperty(exports, "clearRequestHistory", { enumerable: true, get: function () { return request_history_1.clearRequestHistory; } });
|
package/dist/linkedin-api.d.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SalesSearchFilters } from './types';
|
|
2
|
+
import type { LinkedInProfile, SalesLeadSearchResult, SearchSalesResult, TypeaheadResult, SalesNavigatorProfile, Company } from './types';
|
|
2
3
|
export declare function getProfileByVanity(vanity: string): Promise<LinkedInProfile>;
|
|
3
4
|
export declare function getProfileByUrn(fsdKey: string): Promise<LinkedInProfile>;
|
|
4
|
-
export declare function searchSalesLeads(keywords: string
|
|
5
|
-
|
|
5
|
+
export declare function searchSalesLeads(keywords: string, options?: {
|
|
6
|
+
start?: number;
|
|
7
|
+
count?: number;
|
|
8
|
+
decorationId?: string;
|
|
9
|
+
filters?: SalesSearchFilters;
|
|
10
|
+
rawQuery?: string;
|
|
11
|
+
}): Promise<SearchSalesResult | SalesLeadSearchResult[]>;
|
|
12
|
+
export declare function getProfilesBatch(vanities: string[], concurrency?: number): Promise<(LinkedInProfile | null)[]>;
|
|
13
|
+
export declare function resolveCompanyUniversalName(universalName: string): Promise<{
|
|
14
|
+
companyId?: string;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function getCompanyById(companyId: string): Promise<Company>;
|
|
17
|
+
export declare function getCompanyByUrl(companyUrl: string): Promise<Company>;
|
|
18
|
+
export declare function typeahead(options: {
|
|
19
|
+
type: string;
|
|
20
|
+
query?: string;
|
|
21
|
+
start?: number;
|
|
22
|
+
count?: number;
|
|
23
|
+
}): Promise<TypeaheadResult>;
|
|
24
|
+
export declare function getSalesNavigatorProfileDetails(profileUrnOrId: string): Promise<SalesNavigatorProfile>;
|