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.
@@ -1,2 +1,16 @@
1
- import type { Company } from '../types';
1
+ import type { Company } from "../types";
2
+ /**
3
+ * Parses a LinkedIn company API response into a structured Company object.
4
+ * Handles multiple response formats (nested data + included array, or flat structure).
5
+ *
6
+ * @param raw - Raw API response from LinkedIn companies endpoint
7
+ * @returns Parsed Company object with name, description, size, logo, etc.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const raw = await executeLinkedInRequest({ url: companyUrl });
12
+ * const company = parseCompany(raw);
13
+ * console.log(company.name, company.sizeLabel);
14
+ * ```
15
+ */
2
16
  export declare function parseCompany(raw: unknown): Company;
@@ -2,37 +2,62 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseCompany = parseCompany;
4
4
  const image_parser_1 = require("./image-parser");
5
+ /**
6
+ * Parses a LinkedIn company API response into a structured Company object.
7
+ * Handles multiple response formats (nested data + included array, or flat structure).
8
+ *
9
+ * @param raw - Raw API response from LinkedIn companies endpoint
10
+ * @returns Parsed Company object with name, description, size, logo, etc.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const raw = await executeLinkedInRequest({ url: companyUrl });
15
+ * const company = parseCompany(raw);
16
+ * console.log(company.name, company.sizeLabel);
17
+ * ```
18
+ */
5
19
  function parseCompany(raw) {
6
20
  const r = raw;
7
21
  // LinkedIn API returns nested structure with data + included array
8
22
  const data = r?.data || r;
9
- const included = Array.isArray(r?.included) ? r.included : [];
23
+ const included = Array.isArray(r?.included)
24
+ ? r.included
25
+ : [];
10
26
  // Find MiniCompany in included array (contains name, logo, universalName)
11
- const miniCompany = included.find(it => String(it?.entityUrn || '').includes('fs_miniCompany') ||
12
- String(it?.$type || '').includes('MiniCompany'));
27
+ const miniCompany = included.find((it) => String(it?.entityUrn || "").includes("fs_miniCompany") ||
28
+ String(it?.$type || "").includes("MiniCompany"));
13
29
  // Extract company ID from multiple possible locations
14
30
  const extractId = (obj) => {
15
31
  if (!obj)
16
- return '';
17
- const id = String(obj.id ?? obj.entityUrn ?? obj.objectUrn ?? '');
32
+ return "";
33
+ const id = String(obj.id ?? obj.entityUrn ?? obj.objectUrn ?? "");
18
34
  // Handle all URN formats: fs_miniCompany, fs_company, fsd_company
19
- return id.replace(/^urn:li:(?:fsd_|fs_)?(?:miniCompany|company):/i, '');
35
+ return id.replace(/^urn:li:(?:fsd_|fs_)?(?:miniCompany|company):/i, "");
20
36
  };
21
37
  // Priority: data.entityUrn > miniCompany.objectUrn > miniCompany.entityUrn > data.id
22
38
  const companyId = extractId(data) || extractId(miniCompany) || extractId(r);
23
39
  const company = {
24
40
  companyId,
25
- universalName: (miniCompany?.universalName || data?.universalName || r?.universalName),
41
+ universalName: (miniCompany?.universalName ||
42
+ data?.universalName ||
43
+ r?.universalName),
26
44
  name: (miniCompany?.name || data?.name || r?.name),
27
45
  websiteUrl: (data?.websiteUrl || r?.websiteUrl),
28
46
  description: (data?.description || r?.description),
29
- sizeLabel: (typeof data?.employeeCountRange === 'string' ? data.employeeCountRange :
30
- data?.employeeCountRange?.localizedName) ||
31
- (typeof r?.employeeCountRange === 'string' ? r.employeeCountRange :
32
- r?.employeeCountRange?.localizedName),
33
- headquarters: data?.basicCompanyInfo?.headquarters ||
34
- r?.headquarterLocation?.defaultLocalizedName ||
35
- r?.headquarter?.defaultLocalizedName,
47
+ sizeLabel: (typeof data?.employeeCountRange === "string"
48
+ ? data.employeeCountRange
49
+ : data?.employeeCountRange
50
+ ?.localizedName) ||
51
+ (typeof r?.employeeCountRange === "string"
52
+ ? r.employeeCountRange
53
+ : r?.employeeCountRange
54
+ ?.localizedName),
55
+ headquarters: data?.basicCompanyInfo
56
+ ?.headquarters ||
57
+ r?.headquarterLocation
58
+ ?.defaultLocalizedName ||
59
+ r?.headquarter
60
+ ?.defaultLocalizedName,
36
61
  companyType: (data?.companyType || r?.companyType),
37
62
  industries: (data?.industries || r?.industries),
38
63
  specialties: (data?.specialties || r?.specialties),
@@ -42,14 +67,17 @@ function parseCompany(raw) {
42
67
  coverUrl: undefined,
43
68
  };
44
69
  // Extract logo - check miniCompany first, then data, then root
45
- const logoVector = miniCompany?.logo?.vectorImage ||
70
+ const logoVector = miniCompany?.logo
71
+ ?.vectorImage ||
46
72
  miniCompany?.logo ||
47
73
  data?.logo?.vectorImage ||
48
74
  r?.logo?.vectorImage ||
49
75
  r?.logoV2?.vectorImage;
50
- const coverVector = data?.coverPhoto?.vectorImage ||
76
+ const coverVector = data?.coverPhoto
77
+ ?.vectorImage ||
51
78
  r?.coverPhoto?.vectorImage ||
52
- r?.backgroundImage?.vectorImage;
79
+ r?.backgroundImage
80
+ ?.vectorImage;
53
81
  const logoUrl = (0, image_parser_1.selectBestImageUrl)(logoVector);
54
82
  const coverUrl = (0, image_parser_1.selectBestImageUrl)(coverVector);
55
83
  if (logoUrl)
@@ -1,2 +1,20 @@
1
- import type { LinkedInProfile } from '../types';
1
+ import type { LinkedInProfile } from "../types";
2
+ /**
3
+ * Parses a LinkedIn profile response from the Voyager API.
4
+ *
5
+ * @param rawResponse - Raw API response containing `included` array with profile entities
6
+ * @param vanity - The public identifier to match against; used to handle API returning multiple profiles
7
+ * @returns Structured LinkedInProfile with positions, education, skills, and metadata
8
+ *
9
+ * @remarks
10
+ * LinkedIn API responses contain an `included` array with various entity types.
11
+ * This parser extracts and correlates:
12
+ * - Profile identity (name, headline, summary, etc.)
13
+ * - Positions with company information
14
+ * - Education history
15
+ * - Skills
16
+ * - Avatar and cover images
17
+ *
18
+ * The parser uses O(n) complexity by building a URN lookup map upfront.
19
+ */
2
20
  export declare function parseFullProfile(rawResponse: unknown, vanity: string): LinkedInProfile;
@@ -2,14 +2,43 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseFullProfile = parseFullProfile;
4
4
  const image_parser_1 = require("./image-parser");
5
+ /**
6
+ * Parses a LinkedIn profile response from the Voyager API.
7
+ *
8
+ * @param rawResponse - Raw API response containing `included` array with profile entities
9
+ * @param vanity - The public identifier to match against; used to handle API returning multiple profiles
10
+ * @returns Structured LinkedInProfile with positions, education, skills, and metadata
11
+ *
12
+ * @remarks
13
+ * LinkedIn API responses contain an `included` array with various entity types.
14
+ * This parser extracts and correlates:
15
+ * - Profile identity (name, headline, summary, etc.)
16
+ * - Positions with company information
17
+ * - Education history
18
+ * - Skills
19
+ * - Avatar and cover images
20
+ *
21
+ * The parser uses O(n) complexity by building a URN lookup map upfront.
22
+ */
5
23
  function parseFullProfile(rawResponse, vanity) {
6
24
  const rr = rawResponse;
7
- const included = Array.isArray(rr?.included) ? rr.included : [];
25
+ const included = Array.isArray(rr?.included)
26
+ ? rr.included
27
+ : [];
28
+ // Build URN lookup map for O(1) entity lookups (optimization from O(n²) to O(n))
29
+ const urnMap = new Map();
30
+ for (const item of included) {
31
+ const rec = item;
32
+ const entityUrn = rec?.entityUrn;
33
+ if (entityUrn) {
34
+ urnMap.set(entityUrn, rec);
35
+ }
36
+ }
8
37
  // FIX: Match profile by publicIdentifier to handle edge case where LinkedIn API
9
38
  // returns multiple profiles (e.g., authenticated user + requested profile)
10
39
  const identity = included.find((it) => {
11
40
  const rec = it;
12
- const isProfile = String(rec?.$type || '').includes('identity.profile.Profile');
41
+ const isProfile = String(rec?.$type || "").includes("identity.profile.Profile");
13
42
  if (!isProfile)
14
43
  return false;
15
44
  // Match by publicIdentifier if vanity was provided
@@ -20,7 +49,8 @@ function parseFullProfile(rawResponse, vanity) {
20
49
  }
21
50
  // Fallback: if no vanity to match, return first profile (legacy behavior for getProfileByUrn)
22
51
  return true;
23
- }) || {};
52
+ }) ||
53
+ {};
24
54
  const profile = {
25
55
  vanity,
26
56
  publicIdentifier: identity.publicIdentifier,
@@ -37,26 +67,27 @@ function parseFullProfile(rawResponse, vanity) {
37
67
  positions: [],
38
68
  educations: [],
39
69
  };
40
- // Extract geoLocation and countryCode
70
+ // Extract geoLocation and countryCode using O(1) lookup
41
71
  const geoLocation = identity.geoLocation;
42
72
  if (geoLocation?.geoUrn) {
43
73
  profile.geoLocationUrn = geoLocation.geoUrn;
44
- // Lookup the geo object in included array to get the actual location name
45
- const geoUrn = geoLocation.geoUrn;
46
- const geoObj = included.find((it) => it?.entityUrn === geoUrn);
74
+ // O(1) lookup for geo object
75
+ const geoObj = urnMap.get(geoLocation.geoUrn);
47
76
  if (geoObj?.defaultLocalizedName) {
48
77
  profile.locationText = geoObj.defaultLocalizedName;
49
78
  }
50
79
  }
51
80
  // Fallback to direct location fields if geo lookup didn't work
52
81
  if (!profile.locationText) {
53
- profile.locationText = identity.locationName || identity.geoLocationName;
82
+ profile.locationText =
83
+ identity.locationName ||
84
+ identity.geoLocationName;
54
85
  }
55
86
  const location = identity.location;
56
87
  if (location?.countryCode) {
57
88
  profile.countryCode = location.countryCode;
58
89
  }
59
- // Positions
90
+ // Helper: Extract company ID from URN
60
91
  function extractCompanyIdFromUrn(urn) {
61
92
  if (!urn)
62
93
  return undefined;
@@ -67,19 +98,20 @@ function parseFullProfile(rawResponse, vanity) {
67
98
  const mSimple = s.match(/company:([^,\s)]+)/i);
68
99
  return mSimple ? mSimple[1] : undefined;
69
100
  }
101
+ // Helper: Deep find company URN in nested object (limited depth)
70
102
  function deepFindCompanyUrn(obj, depth = 0) {
71
103
  if (!obj || depth > 3)
72
104
  return undefined;
73
- if (typeof obj === 'string' && /urn:li:(?:fsd_)?company:/i.test(obj))
105
+ if (typeof obj === "string" && /urn:li:(?:fsd_)?company:/i.test(obj))
74
106
  return obj;
75
- if (typeof obj !== 'object')
107
+ if (typeof obj !== "object")
76
108
  return undefined;
77
109
  const rec = obj;
78
110
  for (const k of Object.keys(rec)) {
79
111
  const v = rec[k];
80
- if (typeof v === 'string' && /urn:li:(?:fsd_)?company:/i.test(v))
112
+ if (typeof v === "string" && /urn:li:(?:fsd_)?company:/i.test(v))
81
113
  return v;
82
- if (v && typeof v === 'object') {
114
+ if (v && typeof v === "object") {
83
115
  const hit = deepFindCompanyUrn(v, depth + 1);
84
116
  if (hit)
85
117
  return hit;
@@ -87,78 +119,95 @@ function parseFullProfile(rawResponse, vanity) {
87
119
  }
88
120
  return undefined;
89
121
  }
122
+ // Single pass through included array for positions, educations, and skills
90
123
  for (const item of included) {
91
124
  const rec = item;
92
- const urn = rec?.entityUrn || '';
93
- // Only match individual positions, exclude position groups
94
- if (!urn.includes('urn:li:fsd_profilePosition:'))
95
- continue;
96
- const pos = {
97
- title: rec?.title,
98
- companyName: rec?.companyName,
99
- description: rec?.description,
100
- isCurrent: !rec?.timePeriod?.endDate && !rec?.dateRange?.end,
101
- startYear: rec?.timePeriod?.startDate?.year || rec?.dateRange?.start?.year,
102
- startMonth: rec?.timePeriod?.startDate?.month || rec?.dateRange?.start?.month,
103
- endYear: rec?.timePeriod?.endDate?.year || rec?.dateRange?.end?.year,
104
- endMonth: rec?.timePeriod?.endDate?.month || rec?.dateRange?.end?.month,
105
- };
106
- // Try to extract company URN and numeric id robustly
107
- const candUrns = [
108
- rec?.companyUrn,
109
- rec?.company?.entityUrn,
110
- rec?.company?.companyUrn,
111
- rec?.companyUrnV2,
112
- ].filter(Boolean);
113
- const foundUrn = candUrns.find(u => /urn:li:(?:fsd_)?company:/i.test(u)) || deepFindCompanyUrn(item);
114
- if (foundUrn) {
115
- pos.companyUrn = foundUrn;
116
- const cid = extractCompanyIdFromUrn(foundUrn);
117
- if (cid)
118
- pos.companyId = cid;
119
- // Extract company logo from included array by dereferencing the URN
120
- const companyObj = included.find(it => it?.entityUrn === foundUrn);
121
- if (companyObj) {
122
- const logoVector = companyObj.logo?.vectorImage;
123
- const logoUrl = (0, image_parser_1.selectBestImageUrl)(logoVector);
124
- if (logoUrl)
125
- pos.companyLogoUrl = logoUrl;
125
+ const urn = rec?.entityUrn || "";
126
+ // Parse positions
127
+ if (urn.includes("urn:li:fsd_profilePosition:")) {
128
+ const pos = {
129
+ title: rec?.title,
130
+ companyName: rec?.companyName,
131
+ description: rec?.description,
132
+ isCurrent: !rec?.timePeriod?.endDate &&
133
+ !rec?.dateRange?.end,
134
+ startYear: rec?.timePeriod
135
+ ?.startDate?.year ||
136
+ rec?.dateRange?.start
137
+ ?.year,
138
+ startMonth: rec?.timePeriod
139
+ ?.startDate?.month ||
140
+ rec?.dateRange?.start
141
+ ?.month,
142
+ endYear: rec?.timePeriod
143
+ ?.endDate?.year ||
144
+ rec?.dateRange?.end
145
+ ?.year,
146
+ endMonth: rec?.timePeriod
147
+ ?.endDate?.month ||
148
+ rec?.dateRange?.end
149
+ ?.month,
150
+ };
151
+ // Try to extract company URN and numeric id robustly
152
+ const candUrns = [
153
+ rec?.companyUrn,
154
+ rec?.company?.entityUrn,
155
+ rec?.company?.companyUrn,
156
+ rec?.companyUrnV2,
157
+ ].filter(Boolean);
158
+ const foundUrn = candUrns.find((u) => /urn:li:(?:fsd_)?company:/i.test(u)) ||
159
+ deepFindCompanyUrn(item);
160
+ if (foundUrn) {
161
+ pos.companyUrn = foundUrn;
162
+ const cid = extractCompanyIdFromUrn(foundUrn);
163
+ if (cid)
164
+ pos.companyId = cid;
165
+ // O(1) lookup for company logo
166
+ const companyObj = urnMap.get(foundUrn);
167
+ if (companyObj) {
168
+ const logoVector = companyObj.logo?.vectorImage;
169
+ const logoUrl = (0, image_parser_1.selectBestImageUrl)(logoVector);
170
+ if (logoUrl)
171
+ pos.companyLogoUrl = logoUrl;
172
+ }
126
173
  }
174
+ profile.positions.push(pos);
175
+ }
176
+ // Parse educations
177
+ else if (urn.includes("fsd_profileEducation")) {
178
+ const edu = {
179
+ schoolName: rec?.schoolName,
180
+ degree: rec?.degreeName,
181
+ fieldOfStudy: rec?.fieldOfStudy,
182
+ startYear: rec?.timePeriod
183
+ ?.startDate?.year ||
184
+ rec?.dateRange?.start
185
+ ?.year,
186
+ startMonth: rec?.timePeriod
187
+ ?.startDate?.month ||
188
+ rec?.dateRange?.start
189
+ ?.month,
190
+ endYear: rec?.timePeriod
191
+ ?.endDate?.year ||
192
+ rec?.dateRange?.end
193
+ ?.year,
194
+ endMonth: rec?.timePeriod
195
+ ?.endDate?.month ||
196
+ rec?.dateRange?.end
197
+ ?.month,
198
+ };
199
+ profile.educations.push(edu);
200
+ }
201
+ // Parse skills
202
+ else if (urn.includes("fsd_skill")) {
203
+ if (!profile.skills)
204
+ profile.skills = [];
205
+ profile.skills.push({
206
+ name: rec?.name,
207
+ entityUrn: urn,
208
+ });
127
209
  }
128
- profile.positions.push(pos);
129
- }
130
- // Educations
131
- for (const item of included) {
132
- const rec = item;
133
- const urn = rec?.entityUrn || '';
134
- if (!urn.includes('fsd_profileEducation'))
135
- continue;
136
- const edu = {
137
- schoolName: rec?.schoolName,
138
- degree: rec?.degreeName,
139
- fieldOfStudy: rec?.fieldOfStudy,
140
- startYear: rec?.timePeriod?.startDate?.year || rec?.dateRange?.start?.year,
141
- startMonth: rec?.timePeriod?.startDate?.month || rec?.dateRange?.start?.month,
142
- endYear: rec?.timePeriod?.endDate?.year || rec?.dateRange?.end?.year,
143
- endMonth: rec?.timePeriod?.endDate?.month || rec?.dateRange?.end?.month,
144
- };
145
- profile.educations.push(edu);
146
- }
147
- // Skills
148
- const skills = [];
149
- for (const item of included) {
150
- const rec = item;
151
- const urn = rec?.entityUrn || '';
152
- if (!urn.includes('fsd_skill'))
153
- continue;
154
- const skill = {
155
- name: rec?.name,
156
- entityUrn: urn,
157
- };
158
- skills.push(skill);
159
210
  }
160
- if (skills.length > 0)
161
- profile.skills = skills;
162
211
  // Avatar - check identity.profilePicture first (primary source)
163
212
  const profilePicture = identity.profilePicture;
164
213
  const displayImageReference = profilePicture?.displayImageReference;
@@ -169,7 +218,8 @@ function parseFullProfile(rawResponse, vanity) {
169
218
  }
170
219
  else {
171
220
  // Fallback: search included array for backward compatibility
172
- const avatarItem = included.find((it) => it?.vectorImage && JSON.stringify(it).includes('vectorImage'));
221
+ const avatarItem = included.find((it) => it?.vectorImage &&
222
+ JSON.stringify(it).includes("vectorImage"));
173
223
  const fallbackVector = avatarItem?.vectorImage;
174
224
  const fallbackUrl = (0, image_parser_1.selectBestImageUrl)(fallbackVector);
175
225
  if (fallbackUrl)
@@ -1,2 +1,2 @@
1
- import type { SalesLeadSearchResult } from '../types';
1
+ import type { SalesLeadSearchResult } from "../types";
2
2
  export declare function parseSalesSearchResults(rawResponse: unknown): SalesLeadSearchResult[];
@@ -4,13 +4,16 @@ exports.parseSalesSearchResults = parseSalesSearchResults;
4
4
  const json_sanitizer_1 = require("../utils/json-sanitizer");
5
5
  function parseSalesSearchResults(rawResponse) {
6
6
  const rr = rawResponse;
7
- const elements = Array.isArray(rr?.elements) ? rr?.elements : [];
7
+ const elements = Array.isArray(rr?.elements)
8
+ ? rr?.elements
9
+ : [];
8
10
  const results = [];
9
11
  for (const el of elements) {
10
- const name = [el?.firstName, el?.lastName].filter(Boolean).join(' ').trim() || undefined;
12
+ const name = [el?.firstName, el?.lastName].filter(Boolean).join(" ").trim() ||
13
+ undefined;
11
14
  const res = {
12
15
  name: (0, json_sanitizer_1.sanitizeForJson)(name),
13
- headline: (0, json_sanitizer_1.sanitizeForJson)((el?.summary || '').split('\n')[0]?.slice(0, 180)),
16
+ headline: (0, json_sanitizer_1.sanitizeForJson)((el?.summary || "").split("\n")[0]?.slice(0, 180)),
14
17
  salesProfileUrn: el?.entityUrn,
15
18
  objectUrn: el?.objectUrn,
16
19
  geoRegion: (0, json_sanitizer_1.sanitizeForJson)(el?.geoRegion),
@@ -18,7 +21,7 @@ function parseSalesSearchResults(rawResponse) {
18
21
  summary: (0, json_sanitizer_1.sanitizeForJson)(el?.summary),
19
22
  };
20
23
  // fsdKey from URN patterns
21
- const urn = String(el?.entityUrn || '');
24
+ const urn = String(el?.entityUrn || "");
22
25
  const m3 = urn.match(/^urn:li:(?:fs_salesProfile|fsd_profile):\(([^,)]+),([^,)]+),([^,)]+)\)/i);
23
26
  if (m3) {
24
27
  res.fsdKey = m3[1]?.trim();
@@ -34,11 +37,13 @@ function parseSalesSearchResults(rawResponse) {
34
37
  const img = el?.profilePictureDisplayImage;
35
38
  if (img?.rootUrl && Array.isArray(img?.artifacts) && img.artifacts.length) {
36
39
  const best = img.artifacts.reduce((p, c) => ((c?.width || 0) > (p?.width || 0) ? c : p), {});
37
- const seg = best?.fileIdentifyingUrlPathSegment || '';
40
+ const seg = best?.fileIdentifyingUrlPathSegment || "";
38
41
  res.imageUrl = img.rootUrl + seg;
39
42
  }
40
43
  // Current position (backward compatible - extract from first position)
41
- const pos = Array.isArray(el?.currentPositions) && el.currentPositions.length > 0 ? el.currentPositions[0] : undefined;
44
+ const pos = Array.isArray(el?.currentPositions) && el.currentPositions.length > 0
45
+ ? el.currentPositions[0]
46
+ : undefined;
42
47
  if (pos) {
43
48
  res.currentRole = (0, json_sanitizer_1.sanitizeForJson)(pos?.title);
44
49
  res.currentCompany = (0, json_sanitizer_1.sanitizeForJson)(pos?.companyName);
@@ -46,9 +51,11 @@ function parseSalesSearchResults(rawResponse) {
46
51
  const companyData = pos?.companyUrnResolutionResult;
47
52
  if (companyData?.companyPictureDisplayImage) {
48
53
  const cImg = companyData.companyPictureDisplayImage;
49
- if (cImg?.rootUrl && Array.isArray(cImg?.artifacts) && cImg.artifacts.length) {
54
+ if (cImg?.rootUrl &&
55
+ Array.isArray(cImg?.artifacts) &&
56
+ cImg.artifacts.length) {
50
57
  const bestC = cImg.artifacts.reduce((p, c) => ((c?.width || 0) > (p?.width || 0) ? c : p), {});
51
- const segC = bestC?.fileIdentifyingUrlPathSegment || '';
58
+ const segC = bestC?.fileIdentifyingUrlPathSegment || "";
52
59
  res.companyLogoUrl = cImg.rootUrl + segC;
53
60
  }
54
61
  }
@@ -65,10 +72,16 @@ function parseSalesSearchResults(rawResponse) {
65
72
  res.pendingInvitation = el?.pendingInvitation;
66
73
  res.blockThirdPartyDataSharing = el?.blockThirdPartyDataSharing;
67
74
  // Pass through full positions arrays
68
- res.currentPositions = Array.isArray(el?.currentPositions) ? el.currentPositions : undefined;
69
- res.pastPositions = Array.isArray(el?.pastPositions) ? el.pastPositions : undefined;
75
+ res.currentPositions = Array.isArray(el?.currentPositions)
76
+ ? el.currentPositions
77
+ : undefined;
78
+ res.pastPositions = Array.isArray(el?.pastPositions)
79
+ ? el.pastPositions
80
+ : undefined;
70
81
  // Pass through spotlight badges
71
- res.spotlightBadges = Array.isArray(el?.spotlightBadges) ? el.spotlightBadges : undefined;
82
+ res.spotlightBadges = Array.isArray(el?.spotlightBadges)
83
+ ? el.spotlightBadges
84
+ : undefined;
72
85
  results.push(res);
73
86
  }
74
87
  return results;
@@ -1,2 +1,2 @@
1
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
2
  export declare function log(level: LogLevel, message: string, data?: unknown): void;
@@ -9,14 +9,20 @@ const LEVELS = {
9
9
  error: 40,
10
10
  };
11
11
  const SECRET_KEYS = new Set([
12
- 'cosiallApiKey',
13
- 'apikey',
14
- 'apiKey',
15
- 'authorization',
16
- 'cookie',
17
- 'password',
18
- 'secret',
19
- 'set-cookie',
12
+ "cosiallApiKey",
13
+ "apikey",
14
+ "apiKey",
15
+ "authorization",
16
+ "cookie",
17
+ "password",
18
+ "secret",
19
+ "set-cookie",
20
+ "csrf",
21
+ "csrfToken",
22
+ "csrf-token",
23
+ "x-csrf-token",
24
+ "li_at",
25
+ "jsessionid",
20
26
  ]);
21
27
  function maskIfSecret(key, value) {
22
28
  if (!key)
@@ -24,27 +30,31 @@ function maskIfSecret(key, value) {
24
30
  const k = key.toLowerCase();
25
31
  for (const s of SECRET_KEYS) {
26
32
  if (k === s.toLowerCase()) {
27
- if (typeof value === 'string') {
33
+ if (typeof value === "string") {
28
34
  if (value.length <= 6)
29
- return '***';
35
+ return "***";
30
36
  return `${value.slice(0, 2)}***${value.slice(-2)}`;
31
37
  }
32
- return '***';
38
+ return "***";
33
39
  }
34
40
  }
35
41
  return value;
36
42
  }
37
43
  function log(level, message, data) {
38
- let configured = 'info';
44
+ let configured = "info";
39
45
  try {
40
- configured = ((0, config_1.getConfig)().logLevel || 'info');
46
+ configured = ((0, config_1.getConfig)().logLevel || "info");
41
47
  }
42
48
  catch {
43
- configured = 'info';
49
+ configured = "info";
44
50
  }
45
51
  if (LEVELS[level] < LEVELS[configured])
46
52
  return;
47
- const payload = { timestamp: new Date().toISOString(), level, message };
53
+ const payload = {
54
+ timestamp: new Date().toISOString(),
55
+ level,
56
+ message,
57
+ };
48
58
  if (data !== undefined) {
49
59
  try {
50
60
  // Safe stringify with masking
@@ -53,15 +63,15 @@ function log(level, message, data) {
53
63
  }
54
64
  catch {
55
65
  // Fallback to shallow copy
56
- payload.data = '[unserializable]';
66
+ payload.data = "[unserializable]";
57
67
  }
58
68
  }
59
69
  const line = JSON.stringify(payload);
60
70
  switch (level) {
61
- case 'error':
71
+ case "error":
62
72
  console.error(line);
63
73
  break;
64
- case 'warn':
74
+ case "warn":
65
75
  console.warn(line);
66
76
  break;
67
77
  default:
@@ -1,4 +1,35 @@
1
- import type { SalesSearchFilters } from '../types';
1
+ import type { SalesSearchFilters } from "../types";
2
+ /**
3
+ * Creates a deterministic string representation of an object for use as cache keys.
4
+ * Sorts object keys recursively to ensure consistent output regardless of property order.
5
+ *
6
+ * @param obj - Any JSON-serializable value
7
+ * @returns Stable string representation suitable for cache key usage
8
+ */
2
9
  export declare function stableStringify(obj: unknown): string;
3
10
  export declare function buildFilterSignature(filters?: SalesSearchFilters, rawQuery?: string): string | undefined;
11
+ /**
12
+ * Builds a Sales Navigator search query string from keywords and filters.
13
+ * Encodes the query in LinkedIn's proprietary format for the salesApiLeadSearch endpoint.
14
+ *
15
+ * @param keywords - Search keywords to include in the query
16
+ * @param filters - Optional SalesSearchFilters object with advanced criteria:
17
+ * - role: seniority_ids, current_titles, past_titles, functions
18
+ * - company: current, past, headcount, type
19
+ * - personal: geography, industry_ids, profile_language, years_* ranges
20
+ * @returns Encoded query string ready for use in Sales Navigator API URL
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // Simple keyword search
25
+ * const query = buildLeadSearchQuery('CEO');
26
+ * // Returns: "(keywords:CEO)"
27
+ *
28
+ * // With filters
29
+ * const query = buildLeadSearchQuery('', {
30
+ * role: { seniority_ids: [9, 10] },
31
+ * company: { headcount: { include: ['51-200', '201-500'] } }
32
+ * });
33
+ * ```
34
+ */
4
35
  export declare function buildLeadSearchQuery(keywords: string, filters?: SalesSearchFilters): string;