linkedin-secret-sauce 0.3.23 → 0.3.25
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 +36 -0
- package/dist/cookie-pool.js +53 -0
- package/dist/linkedin-api.js +87 -6
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/cookie-pool.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { LinkedInCookie } from './types';
|
|
2
|
+
interface AccountEntry {
|
|
3
|
+
accountId: string;
|
|
4
|
+
cookies: LinkedInCookie[];
|
|
5
|
+
expiresAt?: number;
|
|
6
|
+
failures: number;
|
|
7
|
+
cooldownUntil: number;
|
|
8
|
+
lastUsedAt?: number;
|
|
9
|
+
}
|
|
2
10
|
export declare function initializeCookiePool(): Promise<void>;
|
|
3
11
|
export declare function getCookiePoolHealth(): {
|
|
4
12
|
initialized: boolean;
|
|
@@ -30,3 +38,31 @@ export declare function buildCookieHeader(cookies: LinkedInCookie[]): string;
|
|
|
30
38
|
export declare function extractCsrfToken(cookies: LinkedInCookie[]): string;
|
|
31
39
|
export declare function adminSetCooldown(accountId: string, ms: number): void;
|
|
32
40
|
export declare function adminResetAccount(accountId: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* TEST-ONLY: Get all account IDs from the pool
|
|
43
|
+
* @returns Array of account IDs in the pool
|
|
44
|
+
*/
|
|
45
|
+
export declare function _testGetAllAccountIds(): string[];
|
|
46
|
+
/**
|
|
47
|
+
* TEST-ONLY: Get cookies for a specific account
|
|
48
|
+
* @param accountId - The account ID to get cookies for
|
|
49
|
+
* @returns Cookies array or undefined if account not found
|
|
50
|
+
*/
|
|
51
|
+
export declare function _testGetAccountCookies(accountId: string): LinkedInCookie[] | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* TEST-ONLY: Get full account entry for diagnostics
|
|
54
|
+
* @param accountId - The account ID to inspect
|
|
55
|
+
* @returns Account entry with all metadata
|
|
56
|
+
*/
|
|
57
|
+
export declare function _testGetAccountEntry(accountId: string): AccountEntry | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* TEST-ONLY: Get current pool state snapshot
|
|
60
|
+
* @returns Pool state for debugging
|
|
61
|
+
*/
|
|
62
|
+
export declare function _testGetPoolState(): {
|
|
63
|
+
initialized: boolean;
|
|
64
|
+
totalAccounts: number;
|
|
65
|
+
currentRRIndex: number;
|
|
66
|
+
accountOrder: string[];
|
|
67
|
+
};
|
|
68
|
+
export {};
|
package/dist/cookie-pool.js
CHANGED
|
@@ -47,6 +47,10 @@ exports.buildCookieHeader = buildCookieHeader;
|
|
|
47
47
|
exports.extractCsrfToken = extractCsrfToken;
|
|
48
48
|
exports.adminSetCooldown = adminSetCooldown;
|
|
49
49
|
exports.adminResetAccount = adminResetAccount;
|
|
50
|
+
exports._testGetAllAccountIds = _testGetAllAccountIds;
|
|
51
|
+
exports._testGetAccountCookies = _testGetAccountCookies;
|
|
52
|
+
exports._testGetAccountEntry = _testGetAccountEntry;
|
|
53
|
+
exports._testGetPoolState = _testGetPoolState;
|
|
50
54
|
const cosiall_client_1 = require("./cosiall-client");
|
|
51
55
|
const config_1 = require("./config");
|
|
52
56
|
const errors_1 = require("./utils/errors");
|
|
@@ -357,3 +361,52 @@ function adminResetAccount(accountId) {
|
|
|
357
361
|
entry.failures = 0;
|
|
358
362
|
entry.cooldownUntil = 0;
|
|
359
363
|
}
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// TEST-ONLY EXPORTS - Not for production use
|
|
366
|
+
// ============================================================================
|
|
367
|
+
// These functions expose internal pool state for diagnostic/testing purposes.
|
|
368
|
+
// They bypass normal selection logic and should only be used in test scripts.
|
|
369
|
+
/**
|
|
370
|
+
* TEST-ONLY: Get all account IDs from the pool
|
|
371
|
+
* @returns Array of account IDs in the pool
|
|
372
|
+
*/
|
|
373
|
+
function _testGetAllAccountIds() {
|
|
374
|
+
if (!poolState.initialized) {
|
|
375
|
+
throw new Error('TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.');
|
|
376
|
+
}
|
|
377
|
+
return Array.from(poolState.accounts.keys());
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* TEST-ONLY: Get cookies for a specific account
|
|
381
|
+
* @param accountId - The account ID to get cookies for
|
|
382
|
+
* @returns Cookies array or undefined if account not found
|
|
383
|
+
*/
|
|
384
|
+
function _testGetAccountCookies(accountId) {
|
|
385
|
+
if (!poolState.initialized) {
|
|
386
|
+
throw new Error('TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.');
|
|
387
|
+
}
|
|
388
|
+
return poolState.accounts.get(accountId)?.cookies;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* TEST-ONLY: Get full account entry for diagnostics
|
|
392
|
+
* @param accountId - The account ID to inspect
|
|
393
|
+
* @returns Account entry with all metadata
|
|
394
|
+
*/
|
|
395
|
+
function _testGetAccountEntry(accountId) {
|
|
396
|
+
if (!poolState.initialized) {
|
|
397
|
+
throw new Error('TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.');
|
|
398
|
+
}
|
|
399
|
+
return poolState.accounts.get(accountId);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* TEST-ONLY: Get current pool state snapshot
|
|
403
|
+
* @returns Pool state for debugging
|
|
404
|
+
*/
|
|
405
|
+
function _testGetPoolState() {
|
|
406
|
+
return {
|
|
407
|
+
initialized: poolState.initialized,
|
|
408
|
+
totalAccounts: poolState.accounts.size,
|
|
409
|
+
currentRRIndex: poolState.rrIndex,
|
|
410
|
+
accountOrder: [...poolState.order],
|
|
411
|
+
};
|
|
412
|
+
}
|
package/dist/linkedin-api.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.COMPANY_SIZE_OPTIONS = exports.INDUSTRY_OPTIONS = exports.LANGUAGE_OPTIONS = exports.REGION_OPTIONS = exports.FUNCTION_OPTIONS = exports.SENIORITY_OPTIONS = exports.YEARS_OF_EXPERIENCE_OPTIONS = exports.YEARS_IN_POSITION_OPTIONS = exports.YEARS_AT_COMPANY_OPTIONS = void 0;
|
|
4
37
|
exports.getProfileByVanity = getProfileByVanity;
|
|
@@ -128,9 +161,17 @@ async function getProfileByUrn(fsdKey) {
|
|
|
128
161
|
return cachedUrn;
|
|
129
162
|
}
|
|
130
163
|
const url = `${LINKEDIN_API_BASE}/identity/dash/profiles?q=memberIdentity&memberIdentity=${encodeURIComponent(keyMatch)}&decorationId=com.linkedin.voyager.dash.deco.identity.profile.FullProfileWithEntities-35`;
|
|
164
|
+
try {
|
|
165
|
+
(0, logger_1.log)('debug', 'api.requestUrl', { operation: 'getProfileByUrn', url, fsdKey: keyMatch });
|
|
166
|
+
}
|
|
167
|
+
catch { }
|
|
131
168
|
let raw;
|
|
132
169
|
try {
|
|
133
170
|
raw = await (0, http_client_1.executeLinkedInRequest)({ url }, 'getProfileByUrn');
|
|
171
|
+
try {
|
|
172
|
+
(0, logger_1.log)('debug', 'api.rawResponse', { operation: 'getProfileByUrn', responseSize: JSON.stringify(raw).length });
|
|
173
|
+
}
|
|
174
|
+
catch { }
|
|
134
175
|
}
|
|
135
176
|
catch (e) {
|
|
136
177
|
const status = e?.status ?? 0;
|
|
@@ -143,9 +184,46 @@ async function getProfileByUrn(fsdKey) {
|
|
|
143
184
|
}
|
|
144
185
|
throw e;
|
|
145
186
|
}
|
|
187
|
+
// Extract publicIdentifier from response to validate correct profile selection
|
|
188
|
+
// BUG FIX: LinkedIn API returns multiple profiles (requested profile + connections/colleagues)
|
|
189
|
+
// The correct profile is referenced in data.*elements[0] - we must match against this URN
|
|
190
|
+
const rr = raw;
|
|
191
|
+
const included = Array.isArray(rr?.included) ? rr.included : [];
|
|
192
|
+
// Get the primary profile URN from data.*elements[0]
|
|
193
|
+
const dataObj = rr?.data;
|
|
194
|
+
const elementsArray = dataObj?.['*elements'];
|
|
195
|
+
const requestedProfileUrn = elementsArray?.[0];
|
|
196
|
+
// Find the profile in included[] that matches the requested URN
|
|
197
|
+
const identityObj = requestedProfileUrn
|
|
198
|
+
? included.find((it) => {
|
|
199
|
+
const rec = it;
|
|
200
|
+
const isProfile = String(rec?.$type || '').includes('identity.profile.Profile');
|
|
201
|
+
if (!isProfile)
|
|
202
|
+
return false;
|
|
203
|
+
// Match the entityUrn against the requested profile URN from data.*elements[0]
|
|
204
|
+
return rec.entityUrn === requestedProfileUrn;
|
|
205
|
+
})
|
|
206
|
+
: undefined;
|
|
207
|
+
const publicIdentifier = identityObj?.publicIdentifier || '';
|
|
208
|
+
try {
|
|
209
|
+
(0, logger_1.log)('debug', 'api.extractedIdentity', {
|
|
210
|
+
operation: 'getProfileByUrn',
|
|
211
|
+
publicIdentifier,
|
|
212
|
+
firstName: identityObj?.firstName,
|
|
213
|
+
lastName: identityObj?.lastName,
|
|
214
|
+
objectUrn: identityObj?.objectUrn,
|
|
215
|
+
profilesInResponse: included.filter(it => String(it?.$type || '').includes('identity.profile.Profile')).length
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch { }
|
|
146
219
|
let prof;
|
|
147
220
|
try {
|
|
148
|
-
|
|
221
|
+
// Pass publicIdentifier to parser so it can validate profile selection (same logic as getProfileByVanity)
|
|
222
|
+
prof = (0, profile_parser_1.parseFullProfile)(raw, publicIdentifier);
|
|
223
|
+
try {
|
|
224
|
+
(0, logger_1.log)('debug', 'api.parsedProfile', { operation: 'getProfileByUrn', firstName: prof.firstName, lastName: prof.lastName, vanity: prof.vanity });
|
|
225
|
+
}
|
|
226
|
+
catch { }
|
|
149
227
|
}
|
|
150
228
|
catch {
|
|
151
229
|
try {
|
|
@@ -167,9 +245,11 @@ async function searchSalesLeads(keywords, options) {
|
|
|
167
245
|
const start = Number.isFinite(options?.start) ? Number(options.start) : 0;
|
|
168
246
|
const count = Number.isFinite(options?.count) ? Number(options.count) : 25;
|
|
169
247
|
const deco = options?.decorationId || 'com.linkedin.sales.deco.desktop.searchv2.LeadSearchResult-14';
|
|
248
|
+
// Generate or use provided sessionId
|
|
249
|
+
const sessionId = options?.sessionId || (await Promise.resolve().then(() => __importStar(require('crypto')))).randomUUID();
|
|
170
250
|
const sig = (0, search_encoder_1.buildFilterSignature)(options?.filters, options?.rawQuery);
|
|
171
251
|
// Phase 2.1: Include sessionId in cache key for session-specific caching
|
|
172
|
-
const cacheKey = JSON.stringify({ k: String(keywords || '').toLowerCase(), start, count, deco, sig, sessionId
|
|
252
|
+
const cacheKey = JSON.stringify({ k: String(keywords || '').toLowerCase(), start, count, deco, sig, sessionId });
|
|
173
253
|
const cached = getCached(searchCache, cacheKey, cfg.searchCacheTtl);
|
|
174
254
|
if (cached) {
|
|
175
255
|
(0, metrics_1.incrementMetric)('searchCacheHits');
|
|
@@ -185,16 +265,16 @@ async function searchSalesLeads(keywords, options) {
|
|
|
185
265
|
async function doRequest(decorationId) {
|
|
186
266
|
const url = `${SALES_NAV_BASE}/salesApiLeadSearch?q=searchQuery&start=${start}&count=${count}&decorationId=${encodeURIComponent(decorationId)}&query=${queryStruct}`;
|
|
187
267
|
try {
|
|
188
|
-
(0, logger_1.log)('info', 'api.start', { operation: 'searchSalesLeads', selector: { keywords, start, count, deco: decorationId, sessionId
|
|
268
|
+
(0, logger_1.log)('info', 'api.start', { operation: 'searchSalesLeads', selector: { keywords, start, count, deco: decorationId, sessionId } });
|
|
189
269
|
}
|
|
190
270
|
catch { }
|
|
191
271
|
const out = await (0, http_client_1.executeLinkedInRequest)({
|
|
192
272
|
url,
|
|
193
273
|
headers: { Referer: 'https://www.linkedin.com/sales/search/people' },
|
|
194
|
-
sessionId
|
|
274
|
+
sessionId, // Phase 2.1: Pass sessionId for account stickiness
|
|
195
275
|
}, 'searchSalesLeads');
|
|
196
276
|
try {
|
|
197
|
-
(0, logger_1.log)('info', 'api.ok', { operation: 'searchSalesLeads', selector: { keywords, start, count, deco: decorationId, sessionId
|
|
277
|
+
(0, logger_1.log)('info', 'api.ok', { operation: 'searchSalesLeads', selector: { keywords, start, count, deco: decorationId, sessionId } });
|
|
198
278
|
}
|
|
199
279
|
catch { }
|
|
200
280
|
return out;
|
|
@@ -213,7 +293,8 @@ async function searchSalesLeads(keywords, options) {
|
|
|
213
293
|
? {
|
|
214
294
|
items,
|
|
215
295
|
page: { start: Number(paging.start ?? start), count: Number(paging.count ?? count), total: paging?.total },
|
|
216
|
-
metadata: metadata?.totalDisplayCount ? { totalDisplayCount: metadata.totalDisplayCount } : undefined
|
|
296
|
+
metadata: metadata?.totalDisplayCount ? { totalDisplayCount: metadata.totalDisplayCount } : undefined,
|
|
297
|
+
_meta: { sessionId } // Return sessionId to consumer
|
|
217
298
|
}
|
|
218
299
|
: items; // backward-compat: old tests expect an array when no options passed
|
|
219
300
|
searchCache.set(cacheKey, { data: result, ts: Date.now() });
|
package/dist/types.d.ts
CHANGED