Skip to content

Commit 41ae34e

Browse files
New Navigator actions
1 parent 735103f commit 41ae34e

47 files changed

Lines changed: 1049 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@linkedapi/node",
3-
"version": "2.3.22",
3+
"version": "2.3.23",
44
"description": "Control your LinkedIn accounts and retrieve real-time data, all through this Node.js SDK.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

‎src/core/operation.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ export const OPERATION_NAME = {
5656
nvSearchCompanies: 'nvSearchCompanies',
5757
nvSearchPeople: 'nvSearchPeople',
5858
nvRetrieveAlerts: 'nvRetrieveAlerts',
59+
nvRetrieveLists: 'nvRetrieveLists',
60+
nvDeleteList: 'nvDeleteList',
61+
nvCreateList: 'nvCreateList',
62+
nvDeleteSavedSearch: 'nvDeleteSavedSearch',
63+
nvCreateSavedSearch: 'nvCreateSavedSearch',
64+
nvRetrieveSavedSearches: 'nvRetrieveSavedSearches',
65+
nvRenameList: 'nvRenameList',
66+
nvRemoveAccountFromList: 'nvRemoveAccountFromList',
67+
nvRemoveLeadFromList: 'nvRemoveLeadFromList',
68+
nvRetrieveListAccounts: 'nvRetrieveListAccounts',
69+
nvRetrieveListLeads: 'nvRetrieveListLeads',
70+
nvUnsaveAccount: 'nvUnsaveAccount',
71+
nvSaveAccount: 'nvSaveAccount',
72+
nvUnsaveLead: 'nvUnsaveLead',
73+
nvSaveLead: 'nvSaveLead',
5974
nvFetchCompany: 'nvFetchCompany',
6075
nvFetchPerson: 'nvFetchPerson',
6176
} as const;

‎src/index.ts‎

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,30 @@ import {
1616
FetchPost,
1717
IgnoreInvitation,
1818
ManageConversation,
19+
NvCreateList,
20+
NvCreateSavedSearch,
21+
NvDeleteList,
22+
NvDeleteSavedSearch,
1923
NvFetchCompany,
2024
NvFetchPerson,
2125
NvManageConversation,
26+
NvRemoveAccountFromList,
27+
NvRemoveLeadFromList,
28+
NvRenameList,
2229
NvRetrieveAlerts,
30+
NvRetrieveListAccounts,
31+
NvRetrieveListLeads,
32+
NvRetrieveLists,
33+
NvRetrieveSavedSearches,
34+
NvSaveAccount,
35+
NvSaveLead,
2336
NvSearchCompanies,
2437
NvSearchPeople,
2538
NvSendMessage,
2639
NvSyncConversation,
2740
NvSyncInbox,
41+
NvUnsaveAccount,
42+
NvUnsaveLead,
2843
ReactToComment,
2944
ReactToPost,
3045
RemoveConnection,
@@ -156,6 +171,21 @@ class LinkedApi {
156171
this.nvFetchCompany = new NvFetchCompany(this.httpClient);
157172
this.nvFetchPerson = new NvFetchPerson(this.httpClient);
158173
this.nvRetrieveAlerts = new NvRetrieveAlerts(this.httpClient);
174+
this.nvRetrieveLists = new NvRetrieveLists(this.httpClient);
175+
this.nvDeleteList = new NvDeleteList(this.httpClient);
176+
this.nvCreateList = new NvCreateList(this.httpClient);
177+
this.nvDeleteSavedSearch = new NvDeleteSavedSearch(this.httpClient);
178+
this.nvCreateSavedSearch = new NvCreateSavedSearch(this.httpClient);
179+
this.nvRetrieveSavedSearches = new NvRetrieveSavedSearches(this.httpClient);
180+
this.nvRenameList = new NvRenameList(this.httpClient);
181+
this.nvRemoveAccountFromList = new NvRemoveAccountFromList(this.httpClient);
182+
this.nvRemoveLeadFromList = new NvRemoveLeadFromList(this.httpClient);
183+
this.nvRetrieveListAccounts = new NvRetrieveListAccounts(this.httpClient);
184+
this.nvRetrieveListLeads = new NvRetrieveListLeads(this.httpClient);
185+
this.nvUnsaveAccount = new NvUnsaveAccount(this.httpClient);
186+
this.nvSaveAccount = new NvSaveAccount(this.httpClient);
187+
this.nvUnsaveLead = new NvUnsaveLead(this.httpClient);
188+
this.nvSaveLead = new NvSaveLead(this.httpClient);
159189

160190
this.operations = [
161191
this.customWorkflow,
@@ -202,6 +232,21 @@ class LinkedApi {
202232
this.nvFetchCompany,
203233
this.nvFetchPerson,
204234
this.nvRetrieveAlerts,
235+
this.nvRetrieveLists,
236+
this.nvDeleteList,
237+
this.nvCreateList,
238+
this.nvDeleteSavedSearch,
239+
this.nvCreateSavedSearch,
240+
this.nvRetrieveSavedSearches,
241+
this.nvRenameList,
242+
this.nvRemoveAccountFromList,
243+
this.nvRemoveLeadFromList,
244+
this.nvRetrieveListAccounts,
245+
this.nvRetrieveListLeads,
246+
this.nvUnsaveAccount,
247+
this.nvSaveAccount,
248+
this.nvUnsaveLead,
249+
this.nvSaveLead,
205250
];
206251
}
207252

@@ -762,7 +807,7 @@ class LinkedApi {
762807
* This method opens a person's profile page in Sales Navigator and retrieves their information.
763808
* Sales Navigator provides enhanced data and is useful for sales prospecting activities.
764809
*
765-
* @param params - Parameters including the person's hashed URL and data options
810+
* @param params - Parameters including the person's public or hashed LinkedIn URL and data options
766811
* @returns Promise resolving to an object containing Sales Navigator person data
767812
*
768813
* @see {@link https://linkedapi.io/docs/visiting-person-page/ Visiting Person Page Documentation}
@@ -833,7 +878,7 @@ class LinkedApi {
833878
* This method opens a company's profile page in Sales Navigator and retrieves their information.
834879
* Sales Navigator provides enhanced company data and is useful for B2B sales prospecting.
835880
*
836-
* @param params - Parameters including the company's hashed URL and data options
881+
* @param params - Parameters including the company's public or hashed LinkedIn URL and data options
837882
* @returns Promise resolving to an object containing Sales Navigator company data
838883
*
839884
* @see {@link https://linkedapi.io/docs/action-nv-open-company-page/ nv.openCompanyPage Action Documentation}
@@ -1132,6 +1177,21 @@ class LinkedApi {
11321177
public nvSearchPeople: NvSearchPeople;
11331178

11341179
public nvRetrieveAlerts: NvRetrieveAlerts;
1180+
public nvRetrieveLists: NvRetrieveLists;
1181+
public nvDeleteList: NvDeleteList;
1182+
public nvCreateList: NvCreateList;
1183+
public nvDeleteSavedSearch: NvDeleteSavedSearch;
1184+
public nvCreateSavedSearch: NvCreateSavedSearch;
1185+
public nvRetrieveSavedSearches: NvRetrieveSavedSearches;
1186+
public nvRenameList: NvRenameList;
1187+
public nvRemoveAccountFromList: NvRemoveAccountFromList;
1188+
public nvRemoveLeadFromList: NvRemoveLeadFromList;
1189+
public nvRetrieveListAccounts: NvRetrieveListAccounts;
1190+
public nvRetrieveListLeads: NvRetrieveListLeads;
1191+
public nvUnsaveAccount: NvUnsaveAccount;
1192+
public nvSaveAccount: NvSaveAccount;
1193+
public nvUnsaveLead: NvUnsaveLead;
1194+
public nvSaveLead: NvSaveLead;
11351195

11361196
/**
11371197
* Send a connection request to a LinkedIn user.
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import { afterEach, describe, expect, it, jest } from '@jest/globals';
2+
3+
import LinkedApi, { Operation, TOperationName } from '../../index';
4+
5+
const LIST_ID = '7420000000000000001' as const;
6+
const PERSON_URL = 'https://www.linkedin.com/in/example-person' as const;
7+
const COMPANY_URL = 'https://www.linkedin.com/company/example-company' as const;
8+
const SEARCH_URL = 'https://www.linkedin.com/sales/search/people?query=example' as const;
9+
const LEAD = {
10+
name: 'Example Person',
11+
urn: 'urn:li:member:123',
12+
hashedUrl: PERSON_URL,
13+
position: null,
14+
companyName: null,
15+
location: null,
16+
addedToListAt: null,
17+
};
18+
const ACCOUNT = {
19+
name: 'Example Company',
20+
urn: 'urn:li:organization:123',
21+
hashedUrl: COMPANY_URL,
22+
industry: null,
23+
employeesCount: null,
24+
};
25+
const CREATED = { id: '7420000000000000002',
26+
name: 'Example',
27+
type: 'lead',
28+
url: SEARCH_URL };
29+
const SAVED_SEARCH = {
30+
...CREATED,
31+
keywords: null,
32+
createdAt: null,
33+
lastViewedAt: null,
34+
newResultsCount: null,
35+
};
36+
const CASES: ReadonlyArray<{
37+
operationName: TOperationName;
38+
actionType: string;
39+
params: Record<string, unknown>;
40+
data?: unknown;
41+
}> = [
42+
{
43+
operationName: 'nvRetrieveListLeads',
44+
actionType: 'nv.retrieveListLeads',
45+
params: { listId: LIST_ID,
46+
limit: 26 },
47+
data: [LEAD],
48+
},
49+
{
50+
operationName: 'nvRetrieveListAccounts',
51+
actionType: 'nv.retrieveListAccounts',
52+
params: { listId: LIST_ID },
53+
data: [ACCOUNT],
54+
},
55+
{
56+
operationName: 'nvRemoveLeadFromList',
57+
actionType: 'nv.removeLeadFromList',
58+
params: { personHashedUrl: PERSON_URL,
59+
listId: LIST_ID },
60+
},
61+
{
62+
operationName: 'nvRemoveAccountFromList',
63+
actionType: 'nv.removeAccountFromList',
64+
params: { companyHashedUrl: COMPANY_URL,
65+
listId: LIST_ID },
66+
},
67+
{
68+
operationName: 'nvRenameList',
69+
actionType: 'nv.renameList',
70+
params: { listId: LIST_ID,
71+
name: 'Prospects' },
72+
data: {
73+
id: LIST_ID,
74+
name: 'Prospects',
75+
type: 'lead',
76+
url: `https://www.linkedin.com/sales/lists/people/${LIST_ID}`,
77+
},
78+
},
79+
{
80+
operationName: 'nvRetrieveSavedSearches',
81+
actionType: 'nv.retrieveSavedSearches',
82+
params: { type: 'lead' },
83+
data: [SAVED_SEARCH],
84+
},
85+
{
86+
operationName: 'nvCreateSavedSearch',
87+
actionType: 'nv.createSavedSearch',
88+
params: { type: 'lead',
89+
customSearchUrl: SEARCH_URL },
90+
data: CREATED,
91+
},
92+
{
93+
operationName: 'nvDeleteSavedSearch',
94+
actionType: 'nv.deleteSavedSearch',
95+
params: { savedSearchId: CREATED.id },
96+
},
97+
];
98+
99+
afterEach((): void => {
100+
jest.restoreAllMocks();
101+
});
102+
103+
describe('Sales Navigator backlog contract', (): void => {
104+
it.each(CASES)(
105+
'maps $operationName root requests and results',
106+
async ({ operationName, actionType, params, data }): Promise<void> => {
107+
const fetchMock = jest.spyOn(globalThis, 'fetch');
108+
fetchMock.mockResolvedValueOnce(
109+
new Response(
110+
JSON.stringify({
111+
success: true,
112+
result: { workflowId: 'nv',
113+
workflowStatus: 'pending' },
114+
}),
115+
),
116+
);
117+
fetchMock.mockResolvedValueOnce(
118+
new Response(
119+
JSON.stringify({
120+
success: true,
121+
result: {
122+
workflowStatus: 'completed',
123+
completion: { actionType,
124+
success: true,
125+
...(data === undefined ? {} : { data }) },
126+
},
127+
}),
128+
),
129+
);
130+
const sdk = new LinkedApi({
131+
linkedApiToken: 'test',
132+
identificationToken: 'test',
133+
baseUrl: 'https://api.test',
134+
});
135+
const operation = sdk.operations.find(
136+
(entry): boolean => entry.operationName === operationName,
137+
) as Operation<Record<string, unknown>, unknown>;
138+
const started = await operation.execute(params);
139+
expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
140+
actionType,
141+
...params,
142+
});
143+
expect(await operation.result(started.workflowId)).toEqual({ data,
144+
errors: [] });
145+
},
146+
);
147+
148+
it.each([
149+
{
150+
operationName: 'nvRenameList',
151+
actionType: 'nv.renameList',
152+
type: 'listNotRenamable',
153+
params: { listId: LIST_ID,
154+
name: 'Prospects' },
155+
},
156+
{
157+
operationName: 'nvDeleteSavedSearch',
158+
actionType: 'nv.deleteSavedSearch',
159+
type: 'savedSearchNotFound',
160+
params: { savedSearchId: CREATED.id },
161+
},
162+
] as const)(
163+
'preserves %s action errors',
164+
async ({ operationName, actionType, type, params }): Promise<void> => {
165+
const error = { type,
166+
message: 'Fixture error' };
167+
const fetchMock = jest.spyOn(globalThis, 'fetch');
168+
fetchMock.mockResolvedValueOnce(
169+
new Response(
170+
JSON.stringify({
171+
success: true,
172+
result: { workflowId: 'nv',
173+
workflowStatus: 'pending' },
174+
}),
175+
),
176+
);
177+
fetchMock.mockResolvedValueOnce(
178+
new Response(
179+
JSON.stringify({
180+
success: true,
181+
result: {
182+
workflowStatus: 'completed',
183+
completion: { actionType,
184+
success: false,
185+
error },
186+
},
187+
}),
188+
),
189+
);
190+
const sdk = new LinkedApi({ linkedApiToken: 'test',
191+
identificationToken: 'test' });
192+
const operation = sdk.operations.find(
193+
(entry): boolean => entry.operationName === operationName,
194+
) as Operation<Record<string, unknown>, unknown>;
195+
const started = await operation.execute(params);
196+
expect(await operation.result(started.workflowId)).toEqual({
197+
data: undefined,
198+
errors: [error],
199+
});
200+
},
201+
);
202+
203+
it('preserves comma-bearing alert identities and list scope', async (): Promise<void> => {
204+
const fetchMock = jest.spyOn(globalThis, 'fetch').mockResolvedValueOnce(
205+
new Response(
206+
JSON.stringify({
207+
success: true,
208+
result: { workflowId: 'nv',
209+
workflowStatus: 'pending' },
210+
}),
211+
),
212+
);
213+
const sdk = new LinkedApi({ linkedApiToken: 'test',
214+
identificationToken: 'test' });
215+
const filter = {
216+
listId: LIST_ID,
217+
subjects: [
218+
'urn:li:fs_salesProfile:(ACwAAAExample,NAME_SEARCH,xyz)',
219+
'urn:li:fs_salesCompany:123',
220+
],
221+
};
222+
await sdk.nvRetrieveAlerts.execute({ sort: 'latest',
223+
filter });
224+
expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
225+
actionType: 'nv.retrieveAlerts',
226+
sort: 'latest',
227+
filter,
228+
});
229+
});
230+
});

0 commit comments

Comments
 (0)