linkedin-secret-sauce 0.3.29 → 0.5.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/enrichment/index.d.ts +43 -0
- package/dist/enrichment/index.js +231 -0
- package/dist/enrichment/orchestrator.d.ts +31 -0
- package/dist/enrichment/orchestrator.js +218 -0
- package/dist/enrichment/providers/apollo.d.ts +11 -0
- package/dist/enrichment/providers/apollo.js +136 -0
- package/dist/enrichment/providers/construct.d.ts +11 -0
- package/dist/enrichment/providers/construct.js +107 -0
- package/dist/enrichment/providers/dropcontact.d.ts +16 -0
- package/dist/enrichment/providers/dropcontact.js +37 -0
- package/dist/enrichment/providers/hunter.d.ts +11 -0
- package/dist/enrichment/providers/hunter.js +162 -0
- package/dist/enrichment/providers/index.d.ts +9 -0
- package/dist/enrichment/providers/index.js +18 -0
- package/dist/enrichment/providers/ldd.d.ts +11 -0
- package/dist/enrichment/providers/ldd.js +110 -0
- package/dist/enrichment/providers/smartprospect.d.ts +11 -0
- package/dist/enrichment/providers/smartprospect.js +249 -0
- package/dist/enrichment/types.d.ts +329 -0
- package/dist/enrichment/types.js +31 -0
- package/dist/enrichment/utils/disposable-domains.d.ts +24 -0
- package/dist/enrichment/utils/disposable-domains.js +1011 -0
- package/dist/enrichment/utils/index.d.ts +6 -0
- package/dist/enrichment/utils/index.js +22 -0
- package/dist/enrichment/utils/personal-domains.d.ts +31 -0
- package/dist/enrichment/utils/personal-domains.js +95 -0
- package/dist/enrichment/utils/validation.d.ts +42 -0
- package/dist/enrichment/utils/validation.js +130 -0
- package/dist/enrichment/verification/index.d.ts +4 -0
- package/dist/enrichment/verification/index.js +8 -0
- package/dist/enrichment/verification/mx.d.ts +16 -0
- package/dist/enrichment/verification/mx.js +168 -0
- package/dist/http-client.d.ts +1 -1
- package/dist/http-client.js +146 -63
- package/dist/index.d.ts +17 -14
- package/dist/index.js +20 -1
- 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
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
import type { Company } from
|
|
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)
|
|
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 ||
|
|
12
|
-
String(it?.$type ||
|
|
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 ||
|
|
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 ===
|
|
30
|
-
data
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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
|
|
76
|
+
const coverVector = data?.coverPhoto
|
|
77
|
+
?.vectorImage ||
|
|
51
78
|
r?.coverPhoto?.vectorImage ||
|
|
52
|
-
r?.backgroundImage
|
|
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
|
|
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)
|
|
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 ||
|
|
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
|
-
//
|
|
45
|
-
const
|
|
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 =
|
|
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
|
-
//
|
|
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 ===
|
|
105
|
+
if (typeof obj === "string" && /urn:li:(?:fsd_)?company:/i.test(obj))
|
|
74
106
|
return obj;
|
|
75
|
-
if (typeof obj !==
|
|
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 ===
|
|
112
|
+
if (typeof v === "string" && /urn:li:(?:fsd_)?company:/i.test(v))
|
|
81
113
|
return v;
|
|
82
|
-
if (v && typeof v ===
|
|
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
|
-
//
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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 &&
|
|
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
|
|
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)
|
|
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(
|
|
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 ||
|
|
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
|
|
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 &&
|
|
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)
|
|
69
|
-
|
|
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)
|
|
82
|
+
res.spotlightBadges = Array.isArray(el?.spotlightBadges)
|
|
83
|
+
? el.spotlightBadges
|
|
84
|
+
: undefined;
|
|
72
85
|
results.push(res);
|
|
73
86
|
}
|
|
74
87
|
return results;
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type LogLevel =
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
2
|
export declare function log(level: LogLevel, message: string, data?: unknown): void;
|
package/dist/utils/logger.js
CHANGED
|
@@ -9,14 +9,20 @@ const LEVELS = {
|
|
|
9
9
|
error: 40,
|
|
10
10
|
};
|
|
11
11
|
const SECRET_KEYS = new Set([
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 ===
|
|
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 =
|
|
44
|
+
let configured = "info";
|
|
39
45
|
try {
|
|
40
|
-
configured = ((0, config_1.getConfig)().logLevel ||
|
|
46
|
+
configured = ((0, config_1.getConfig)().logLevel || "info");
|
|
41
47
|
}
|
|
42
48
|
catch {
|
|
43
|
-
configured =
|
|
49
|
+
configured = "info";
|
|
44
50
|
}
|
|
45
51
|
if (LEVELS[level] < LEVELS[configured])
|
|
46
52
|
return;
|
|
47
|
-
const payload = {
|
|
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 =
|
|
66
|
+
payload.data = "[unserializable]";
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
const line = JSON.stringify(payload);
|
|
60
70
|
switch (level) {
|
|
61
|
-
case
|
|
71
|
+
case "error":
|
|
62
72
|
console.error(line);
|
|
63
73
|
break;
|
|
64
|
-
case
|
|
74
|
+
case "warn":
|
|
65
75
|
console.warn(line);
|
|
66
76
|
break;
|
|
67
77
|
default:
|
|
@@ -1,4 +1,35 @@
|
|
|
1
|
-
import type { SalesSearchFilters } from
|
|
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;
|