linkedin-secret-sauce 0.1.2 → 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/dist/config.js CHANGED
@@ -11,7 +11,6 @@ function initializeLinkedInClient(config) {
11
11
  }
12
12
  // Validate URL format
13
13
  try {
14
- // eslint-disable-next-line no-new
15
14
  new URL(config.cosiallApiUrl);
16
15
  }
17
16
  catch {
@@ -89,7 +89,8 @@ async function ensureInitialized() {
89
89
  (0, logger_1.log)('info', 'cookiePool.refreshed', { count: refreshed.length });
90
90
  }
91
91
  catch (e) {
92
- (0, logger_1.log)('warn', 'cookiePool.refreshFailed', { error: e?.message });
92
+ const err = e;
93
+ (0, logger_1.log)('warn', 'cookiePool.refreshFailed', { error: err?.message });
93
94
  }
94
95
  }, interval);
95
96
  }
@@ -29,9 +29,24 @@ async function fetchCookiesFromCosiall() {
29
29
  }
30
30
  (0, logger_1.log)('info', 'cosiall.fetch.success', { count: data.length });
31
31
  (0, metrics_1.incrementMetric)('cosiallSuccess');
32
- return data.map((item) => ({
33
- accountId: item.accountId,
34
- cookies: Array.isArray(item.cookies) ? item.cookies : [],
35
- expiresAt: item.expiresAt,
36
- }));
32
+ function isCookie(obj) {
33
+ return !!obj && typeof obj === 'object' && 'name' in obj && 'value' in obj;
34
+ }
35
+ function isItem(obj) {
36
+ if (!obj || typeof obj !== 'object')
37
+ return false;
38
+ const rec = obj;
39
+ if (typeof rec.accountId !== 'string')
40
+ return false;
41
+ if (!Array.isArray(rec.cookies))
42
+ return false;
43
+ if (!rec.cookies.every(isCookie))
44
+ return false;
45
+ if (rec.expiresAt !== undefined && typeof rec.expiresAt !== 'number')
46
+ return false;
47
+ return true;
48
+ }
49
+ return data
50
+ .filter(isItem)
51
+ .map((item) => ({ accountId: item.accountId, cookies: item.cookies, expiresAt: item.expiresAt }));
37
52
  }
@@ -1,7 +1,7 @@
1
1
  export interface LinkedInRequestOptions {
2
2
  url: string;
3
3
  method?: 'GET' | 'POST';
4
- body?: any;
4
+ body?: unknown;
5
5
  headers?: Record<string, string>;
6
6
  }
7
7
  export declare function executeLinkedInRequest<T>(options: LinkedInRequestOptions, _operationName: string): Promise<T>;
@@ -71,18 +71,22 @@ async function executeLinkedInRequest(options, _operationName) {
71
71
  (0, logger_1.log)('debug', 'http.attempt', { accountId, attempt: attempt + 1, method: options.method ?? 'GET', url: options.url });
72
72
  let res;
73
73
  try {
74
- res = await fetch(options.url, {
74
+ const init = {
75
75
  method: options.method ?? 'GET',
76
76
  headers,
77
- body: options.body ? JSON.stringify(options.body) : undefined,
78
- });
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));
79
82
  }
80
83
  catch (err) {
81
- const code = err?.code || err?.cause?.code || 'FETCH_FAILED';
82
- (0, logger_1.log)('error', 'http.networkError', { accountId, url: options.url, code, message: String(err?.message || 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) });
83
87
  (0, metrics_1.incrementMetric)('httpFailures');
84
88
  try {
85
- (0, request_history_1.recordRequest)({ operation: op, selector: options.url, status: 0, durationMs: Date.now() - started, accountId, errorMessage: String(err?.message || err) });
89
+ (0, request_history_1.recordRequest)({ operation: op, selector: options.url, status: 0, durationMs: Date.now() - started, accountId, errorMessage: String(e?.message || err) });
86
90
  }
87
91
  catch { }
88
92
  lastError = new errors_1.LinkedInClientError('LinkedIn fetch failed', 'REQUEST_FAILED', 0, accountId);
@@ -210,7 +210,10 @@ async function searchSalesLeads(keywords, options) {
210
210
  }
211
211
  }
212
212
  const items = (0, search_parser_1.parseSalesSearchResults)(raw);
213
- const paging = raw?.paging ?? { start, count };
213
+ const rrec = (raw && typeof raw === 'object') ? raw : undefined;
214
+ const pagingVal = rrec && 'paging' in rrec ? rrec.paging : undefined;
215
+ const p = (pagingVal && typeof pagingVal === 'object') ? pagingVal : undefined;
216
+ const paging = p ?? { start, count };
214
217
  const result = options
215
218
  ? { items, page: { start: Number(paging.start ?? start), count: Number(paging.count ?? count), total: paging?.total } }
216
219
  : items; // backward-compat: old tests expect an array when no options passed
@@ -223,11 +226,8 @@ async function getProfilesBatch(vanities, concurrency = 4) {
223
226
  const results = Array.from({ length: vanities.length }, () => null);
224
227
  let idx = 0;
225
228
  async function worker() {
226
- // eslint-disable-next-line no-constant-condition
227
- while (true) {
229
+ while (idx < vanities.length) {
228
230
  const myIdx = idx++;
229
- if (myIdx >= vanities.length)
230
- break;
231
231
  const vanity = vanities[myIdx];
232
232
  try {
233
233
  const prof = await getProfileByVanity(vanity);
@@ -253,7 +253,10 @@ async function resolveCompanyUniversalName(universalName) {
253
253
  }
254
254
  catch { }
255
255
  const raw = await (0, http_client_1.executeLinkedInRequest)({ url }, 'resolveCompanyUniversalName');
256
- const id = raw?.elements?.[0]?.id ?? raw?.elements?.[0]?.entityUrn ?? undefined;
256
+ const rec = (raw && typeof raw === 'object') ? raw : undefined;
257
+ const elementsVal = rec && 'elements' in rec ? rec.elements : undefined;
258
+ const first = Array.isArray(elementsVal) ? elementsVal[0] : undefined;
259
+ const id = first?.id ?? first?.entityUrn ?? undefined;
257
260
  try {
258
261
  (0, logger_1.log)('info', 'api.ok', { operation: 'resolveCompanyUniversalName', selector: universalName, id });
259
262
  }
@@ -372,11 +375,18 @@ async function typeahead(options) {
372
375
  const items = Array.isArray(raw?.elements)
373
376
  ? raw.elements.map((it) => ({
374
377
  id: String(it?.id ?? it?.backendId ?? ''),
375
- text: it?.displayValue ?? it?.headline?.text ?? it?.text ?? it?.name ?? it?.label ?? ''
378
+ text: it.displayValue ??
379
+ it.headline?.text ??
380
+ it.text ??
381
+ it.name ??
382
+ it.label ??
383
+ ''
376
384
  }))
377
385
  : [];
378
- const paging = raw?.paging ?? {};
379
- const result = { items, page: { start: Number(paging?.start ?? start), count: Number(paging?.count ?? count), total: paging?.total } };
386
+ const rtype = (raw && typeof raw === 'object') ? raw : undefined;
387
+ const pagingVal2 = rtype && 'paging' in rtype ? rtype.paging : undefined;
388
+ const paging2 = (pagingVal2 && typeof pagingVal2 === 'object') ? pagingVal2 : undefined;
389
+ const result = { items, page: { start: Number(paging2?.start ?? start), count: Number(paging2?.count ?? count), total: paging2?.total } };
380
390
  typeaheadCache.set(cacheKey, { data: result, ts: Date.now() });
381
391
  try {
382
392
  (0, logger_1.log)('info', 'api.ok', { operation: 'typeahead', selector: { type, query }, count: items.length });
@@ -1,2 +1,2 @@
1
1
  import type { Company } from '../types';
2
- export declare function parseCompany(raw: any): Company;
2
+ export declare function parseCompany(raw: unknown): Company;
@@ -3,18 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseCompany = parseCompany;
4
4
  const image_parser_1 = require("./image-parser");
5
5
  function parseCompany(raw) {
6
+ const r = raw;
6
7
  const company = {
7
- companyId: String(raw?.id ?? '').replace(/^urn:li:(?:fsd_)?company:/i, '') || String(raw?.entityUrn ?? '').replace(/^urn:li:(?:fsd_)?company:/i, ''),
8
- universalName: raw?.universalName,
9
- name: raw?.name,
10
- websiteUrl: raw?.websiteUrl,
11
- sizeLabel: raw?.employeeCountRange?.localizedName,
12
- headquarters: raw?.headquarterLocation?.defaultLocalizedName || raw?.headquarter?.defaultLocalizedName,
8
+ companyId: String(r?.id ?? '')
9
+ .replace(/^urn:li:(?:fsd_)?company:/i, '') ||
10
+ String(r?.entityUrn ?? '')
11
+ .replace(/^urn:li:(?:fsd_)?company:/i, ''),
12
+ universalName: r?.universalName,
13
+ name: r?.name,
14
+ websiteUrl: r?.websiteUrl,
15
+ sizeLabel: r?.employeeCountRange?.localizedName,
16
+ headquarters: r?.headquarterLocation?.defaultLocalizedName ||
17
+ r?.headquarter?.defaultLocalizedName,
13
18
  logoUrl: undefined,
14
19
  coverUrl: undefined,
15
20
  };
16
- const logoVector = raw?.logo?.vectorImage || raw?.logoV2?.vectorImage;
17
- const coverVector = raw?.coverPhoto?.vectorImage || raw?.backgroundImage?.vectorImage;
21
+ const logoVector = r?.logo?.vectorImage || r?.logoV2?.vectorImage;
22
+ const coverVector = r?.coverPhoto?.vectorImage || r?.backgroundImage?.vectorImage;
18
23
  const logoUrl = (0, image_parser_1.selectBestImageUrl)(logoVector);
19
24
  const coverUrl = (0, image_parser_1.selectBestImageUrl)(coverVector);
20
25
  if (logoUrl)
@@ -1 +1,10 @@
1
- export declare function selectBestImageUrl(vector: any): string | undefined;
1
+ export type VectorArtifact = {
2
+ width?: number;
3
+ fileIdentifyingUrlPathSegment?: string;
4
+ url?: string;
5
+ };
6
+ export type VectorImage = {
7
+ rootUrl?: string;
8
+ artifacts?: VectorArtifact[];
9
+ };
10
+ export declare function selectBestImageUrl(vector: unknown): string | undefined;
@@ -2,14 +2,15 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.selectBestImageUrl = selectBestImageUrl;
4
4
  function selectBestImageUrl(vector) {
5
- if (!vector || !Array.isArray(vector.artifacts) || vector.artifacts.length === 0)
5
+ const v = vector;
6
+ if (!v || !Array.isArray(v.artifacts) || v.artifacts.length === 0)
6
7
  return undefined;
7
- const best = vector.artifacts.reduce((prev, curr) => ((curr?.width || 0) > (prev?.width || 0) ? curr : prev), {});
8
+ const best = v.artifacts.reduce((prev, curr) => ((curr?.width || 0) > (prev?.width || 0) ? curr : prev), {});
8
9
  const seg = best?.fileIdentifyingUrlPathSegment || best?.url;
9
10
  if (!seg)
10
11
  return undefined;
11
12
  if (/^https?:\/\//i.test(seg))
12
13
  return seg;
13
- const root = String(vector.rootUrl || '');
14
+ const root = String(v.rootUrl || '');
14
15
  return root + seg;
15
16
  }
@@ -1,2 +1,2 @@
1
1
  import type { LinkedInProfile } from '../types';
2
- export declare function parseFullProfile(rawResponse: any, vanity: string): LinkedInProfile;
2
+ export declare function parseFullProfile(rawResponse: unknown, vanity: string): LinkedInProfile;
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseFullProfile = parseFullProfile;
4
4
  const image_parser_1 = require("./image-parser");
5
5
  function parseFullProfile(rawResponse, vanity) {
6
- const included = Array.isArray(rawResponse?.included) ? rawResponse.included : [];
6
+ const rr = rawResponse;
7
+ const included = Array.isArray(rr?.included) ? rr.included : [];
7
8
  const identity = included.find((it) => String(it?.$type || '').includes('identity.profile.Profile')) || {};
8
9
  const profile = {
9
10
  vanity,
@@ -33,8 +34,9 @@ function parseFullProfile(rawResponse, vanity) {
33
34
  return obj;
34
35
  if (typeof obj !== 'object')
35
36
  return undefined;
36
- for (const k of Object.keys(obj)) {
37
- const v = obj[k];
37
+ const rec = obj;
38
+ for (const k of Object.keys(rec)) {
39
+ const v = rec[k];
38
40
  if (typeof v === 'string' && /urn:li:(?:fsd_)?company:/i.test(v))
39
41
  return v;
40
42
  if (v && typeof v === 'object') {
@@ -46,25 +48,26 @@ function parseFullProfile(rawResponse, vanity) {
46
48
  return undefined;
47
49
  }
48
50
  for (const item of included) {
49
- const urn = item?.entityUrn || '';
51
+ const rec = item;
52
+ const urn = rec?.entityUrn || '';
50
53
  if (!urn.includes('fsd_profilePosition'))
51
54
  continue;
52
55
  const pos = {
53
- title: item?.title,
54
- companyName: item?.companyName,
55
- description: item?.description,
56
- isCurrent: !item?.timePeriod?.endDate,
57
- startYear: item?.timePeriod?.startDate?.year,
58
- startMonth: item?.timePeriod?.startDate?.month,
59
- endYear: item?.timePeriod?.endDate?.year,
60
- endMonth: item?.timePeriod?.endDate?.month,
56
+ title: rec?.title,
57
+ companyName: rec?.companyName,
58
+ description: rec?.description,
59
+ isCurrent: !rec?.timePeriod?.endDate,
60
+ startYear: rec?.timePeriod?.startDate?.year,
61
+ startMonth: rec?.timePeriod?.startDate?.month,
62
+ endYear: rec?.timePeriod?.endDate?.year,
63
+ endMonth: rec?.timePeriod?.endDate?.month,
61
64
  };
62
65
  // Try to extract company URN and numeric id robustly
63
66
  const candUrns = [
64
- item?.companyUrn,
65
- item?.company?.entityUrn,
66
- item?.company?.companyUrn,
67
- item?.companyUrnV2,
67
+ rec?.companyUrn,
68
+ rec?.company?.entityUrn,
69
+ rec?.company?.companyUrn,
70
+ rec?.companyUrnV2,
68
71
  ].filter(Boolean);
69
72
  const foundUrn = candUrns.find(u => /urn:li:(?:fsd_)?company:/i.test(u)) || deepFindCompanyUrn(item);
70
73
  if (foundUrn) {
@@ -77,17 +80,18 @@ function parseFullProfile(rawResponse, vanity) {
77
80
  }
78
81
  // Educations
79
82
  for (const item of included) {
80
- const urn = item?.entityUrn || '';
83
+ const rec = item;
84
+ const urn = rec?.entityUrn || '';
81
85
  if (!urn.includes('fsd_profileEducation'))
82
86
  continue;
83
87
  const edu = {
84
- schoolName: item?.schoolName,
85
- degree: item?.degreeName,
86
- fieldOfStudy: item?.fieldOfStudy,
87
- startYear: item?.timePeriod?.startDate?.year,
88
- startMonth: item?.timePeriod?.startDate?.month,
89
- endYear: item?.timePeriod?.endDate?.year,
90
- endMonth: item?.timePeriod?.endDate?.month,
88
+ schoolName: rec?.schoolName,
89
+ degree: rec?.degreeName,
90
+ fieldOfStudy: rec?.fieldOfStudy,
91
+ startYear: rec?.timePeriod?.startDate?.year,
92
+ startMonth: rec?.timePeriod?.startDate?.month,
93
+ endYear: rec?.timePeriod?.endDate?.year,
94
+ endMonth: rec?.timePeriod?.endDate?.month,
91
95
  };
92
96
  profile.educations.push(edu);
93
97
  }
@@ -1,2 +1,2 @@
1
1
  import type { SalesLeadSearchResult } from '../types';
2
- export declare function parseSalesSearchResults(rawResponse: any): SalesLeadSearchResult[];
2
+ export declare function parseSalesSearchResults(rawResponse: unknown): SalesLeadSearchResult[];
@@ -2,7 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseSalesSearchResults = parseSalesSearchResults;
4
4
  function parseSalesSearchResults(rawResponse) {
5
- const elements = Array.isArray(rawResponse?.elements) ? rawResponse.elements : [];
5
+ const rr = rawResponse;
6
+ const elements = Array.isArray(rr?.elements) ? rr?.elements : [];
6
7
  const results = [];
7
8
  for (const el of elements) {
8
9
  const name = [el?.firstName, el?.lastName].filter(Boolean).join(' ').trim() || undefined;
@@ -1,2 +1,2 @@
1
1
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2
- export declare function log(level: LogLevel, message: string, data?: any): void;
2
+ export declare function log(level: LogLevel, message: string, data?: unknown): void;
@@ -19,4 +19,7 @@ export interface Metrics {
19
19
  typeaheadRequests: number;
20
20
  }
21
21
  export declare function incrementMetric(key: keyof Metrics, by?: number): void;
22
- export declare function getSnapshot(): Metrics;
22
+ export interface MetricsSnapshot extends Metrics {
23
+ requestHistorySize: number;
24
+ }
25
+ export declare function getSnapshot(): MetricsSnapshot;
@@ -24,7 +24,6 @@ const metrics = {
24
24
  typeaheadRequests: 0,
25
25
  };
26
26
  function incrementMetric(key, by = 1) {
27
- // @ts-ignore - index signature not declared, but keys are enforced by type
28
27
  metrics[key] = (metrics[key] || 0) + by;
29
28
  }
30
29
  function getSnapshot() {
@@ -1,4 +1,4 @@
1
1
  import type { SalesSearchFilters } from '../types';
2
- export declare function stableStringify(obj: any): string;
2
+ export declare function stableStringify(obj: unknown): string;
3
3
  export declare function buildFilterSignature(filters?: SalesSearchFilters, rawQuery?: string): string | undefined;
4
4
  export declare function buildLeadSearchQuery(keywords: string, filters?: SalesSearchFilters): string;
@@ -9,8 +9,9 @@ function stableStringify(obj) {
9
9
  return JSON.stringify(obj);
10
10
  if (Array.isArray(obj))
11
11
  return '[' + obj.map((v) => stableStringify(v)).join(',') + ']';
12
- const keys = Object.keys(obj).sort();
13
- return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';
12
+ const rec = obj;
13
+ const keys = Object.keys(rec).sort();
14
+ return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(rec[k])).join(',') + '}';
14
15
  }
15
16
  function buildFilterSignature(filters, rawQuery) {
16
17
  if (rawQuery)
package/package.json CHANGED
@@ -1,54 +1,59 @@
1
- {
2
- "name": "linkedin-secret-sauce",
3
- "version": "0.1.2",
4
- "description": "Private LinkedIn Sales Navigator client with automatic cookie management",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist"
9
- ],
10
- "publishConfig": {
11
- "registry": "https://registry.npmjs.org/"
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
- "dev": "tsc -w -p tsconfig.json",
19
- "test": "vitest run",
20
- "prepare": "husky",
21
- "prepublishOnly": "npm run build",
22
- "release:patch": "npm version patch && git push --follow-tags",
23
- "release:minor": "npm version minor && git push --follow-tags",
24
- "release:major": "npm version major && git push --follow-tags"
25
- },
26
- "keywords": [
27
- "linkedin",
28
- "sales-navigator",
29
- "client",
30
- "typescript"
31
- ],
32
- "author": "Your Company",
33
- "license": "UNLICENSED",
34
- "engines": {
35
- "node": ">=18.0.0"
36
- },
37
- "repository": {
38
- "type": "git",
39
- "url": "git+https://github.com/enerage/LinkedInSecretSauce.git"
40
- },
41
- "bugs": {
42
- "url": "https://github.com/enerage/LinkedInSecretSauce/issues"
43
- },
44
- "homepage": "https://github.com/enerage/LinkedInSecretSauce#readme",
45
- "devDependencies": {
46
- "@types/node": "^20.11.0",
47
- "typescript": "^5.3.3",
48
- "vitest": "^1.6.0",
49
- "@commitlint/cli": "^19.4.0",
50
- "@commitlint/config-conventional": "^19.4.0",
51
- "husky": "^9.0.11"
52
- }
53
- }
1
+ {
2
+ "name": "linkedin-secret-sauce",
3
+ "version": "0.2.0",
4
+ "description": "Private LinkedIn Sales Navigator client with automatic cookie management",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "publishConfig": {
11
+ "registry": "https://registry.npmjs.org/"
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
+ "prepare": "husky",
23
+ "prepublishOnly": "npm run build",
24
+ "release:patch": "npm version patch && git push --follow-tags",
25
+ "release:minor": "npm version minor && git push --follow-tags",
26
+ "release:major": "npm version major && git push --follow-tags"
27
+ },
28
+ "keywords": [
29
+ "linkedin",
30
+ "sales-navigator",
31
+ "client",
32
+ "typescript"
33
+ ],
34
+ "author": "Your Company",
35
+ "license": "UNLICENSED",
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/enerage/LinkedInSecretSauce.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/enerage/LinkedInSecretSauce/issues"
45
+ },
46
+ "homepage": "https://github.com/enerage/LinkedInSecretSauce#readme",
47
+ "devDependencies": {
48
+ "@types/node": "^20.11.0",
49
+ "@typescript-eslint/eslint-plugin": "^8.9.0",
50
+ "@typescript-eslint/parser": "^8.9.0",
51
+ "eslint": "^9.12.0",
52
+ "typescript": "^5.3.3",
53
+ "vitest": "^1.6.0",
54
+ "@commitlint/cli": "^19.4.0",
55
+ "@commitlint/config-conventional": "^19.4.0",
56
+ "husky": "^9.0.11"
57
+ }
58
+ }
54
59