linkedin-secret-sauce 0.3.28 → 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.
- 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/http-client.d.ts +1 -1
- package/dist/http-client.js +146 -63
- 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 +104 -49
- package/dist/utils/sentry.d.ts +1 -1
- package/dist/utils/sentry.js +56 -8
- package/package.json +1 -1
package/dist/cookie-pool.d.ts
CHANGED
package/dist/cookie-pool.js
CHANGED
|
@@ -74,7 +74,7 @@ function nowSec() {
|
|
|
74
74
|
return Math.floor(Date.now() / 1000);
|
|
75
75
|
}
|
|
76
76
|
function toEpochSeconds(v) {
|
|
77
|
-
if (typeof v !==
|
|
77
|
+
if (typeof v !== "number")
|
|
78
78
|
return undefined;
|
|
79
79
|
if (!Number.isFinite(v) || v <= 0)
|
|
80
80
|
return undefined;
|
|
@@ -82,7 +82,7 @@ function toEpochSeconds(v) {
|
|
|
82
82
|
return v > 1e10 ? Math.floor(v / 1000) : Math.floor(v);
|
|
83
83
|
}
|
|
84
84
|
function isExpired(entry) {
|
|
85
|
-
const jsession = entry.cookies.find(c => String(c.name).toUpperCase() ===
|
|
85
|
+
const jsession = entry.cookies.find((c) => String(c.name).toUpperCase() === "JSESSIONID");
|
|
86
86
|
const jsExp = toEpochSeconds(jsession?.expires);
|
|
87
87
|
if (jsExp && jsExp < nowSec())
|
|
88
88
|
return true;
|
|
@@ -110,10 +110,10 @@ async function ensureInitialized() {
|
|
|
110
110
|
}
|
|
111
111
|
poolState.rrIndex = 0;
|
|
112
112
|
poolState.initialized = true;
|
|
113
|
-
(0, logger_1.log)(
|
|
113
|
+
(0, logger_1.log)("info", "cookiePool.initialized", { count: accounts.length });
|
|
114
114
|
// Start lightweight periodic refresh (guard against duplicate timers)
|
|
115
|
-
if (!poolState.refreshTimer && process.env.NODE_ENV !==
|
|
116
|
-
const interval = Math.max(60_000, (
|
|
115
|
+
if (!poolState.refreshTimer && process.env.NODE_ENV !== "test") {
|
|
116
|
+
const interval = Math.max(60_000, (0, config_1.getConfig)().cookieRefreshInterval ?? 15 * 60 * 1000);
|
|
117
117
|
poolState.refreshTimer = setInterval(async () => {
|
|
118
118
|
try {
|
|
119
119
|
const refreshed = await (0, cosiall_client_1.fetchCookiesFromCosiall)();
|
|
@@ -133,21 +133,36 @@ async function ensureInitialized() {
|
|
|
133
133
|
}
|
|
134
134
|
poolState.accounts = newMap;
|
|
135
135
|
poolState.order = newOrder;
|
|
136
|
-
(0, logger_1.log)(
|
|
136
|
+
(0, logger_1.log)("info", "cookiePool.refreshed", { count: refreshed.length });
|
|
137
137
|
}
|
|
138
138
|
catch (e) {
|
|
139
139
|
const err = e;
|
|
140
|
-
(0, logger_1.log)(
|
|
140
|
+
(0, logger_1.log)("warn", "cookiePool.refreshFailed", { error: err?.message });
|
|
141
141
|
// Report to Sentry if configured
|
|
142
142
|
try {
|
|
143
|
-
const { reportWarningToSentry } = await Promise.resolve().then(() => __importStar(require(
|
|
144
|
-
reportWarningToSentry(
|
|
143
|
+
const { reportWarningToSentry } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
144
|
+
reportWarningToSentry("Cookie refresh failed", {
|
|
145
145
|
error: err?.message,
|
|
146
|
-
tags: { component:
|
|
146
|
+
tags: { component: "cookie-pool", severity: "medium" },
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
catch { }
|
|
150
150
|
}
|
|
151
|
+
// Cleanup expired sessions to prevent memory leaks in long-running processes
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
let expiredCount = 0;
|
|
154
|
+
for (const [sessionId, session] of sessionAccountMap.entries()) {
|
|
155
|
+
if (now > session.expiresAt) {
|
|
156
|
+
sessionAccountMap.delete(sessionId);
|
|
157
|
+
expiredCount++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (expiredCount > 0) {
|
|
161
|
+
(0, logger_1.log)("debug", "cookiePool.sessionCleanup", {
|
|
162
|
+
expiredSessions: expiredCount,
|
|
163
|
+
remainingSessions: sessionAccountMap.size,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
151
166
|
}, interval);
|
|
152
167
|
}
|
|
153
168
|
}
|
|
@@ -171,15 +186,15 @@ function getCookiePoolHealth() {
|
|
|
171
186
|
return {
|
|
172
187
|
initialized: true,
|
|
173
188
|
totalAccounts: accounts.length,
|
|
174
|
-
healthyAccounts: accounts.filter(a => !isExpired(a) && a.failures === 0 && a.cooldownUntil <= now).length,
|
|
175
|
-
expiredAccounts: accounts.filter(a => isExpired(a)).length,
|
|
176
|
-
coolingDownAccounts: accounts.filter(a => a.cooldownUntil > now).length,
|
|
189
|
+
healthyAccounts: accounts.filter((a) => !isExpired(a) && a.failures === 0 && a.cooldownUntil <= now).length,
|
|
190
|
+
expiredAccounts: accounts.filter((a) => isExpired(a)).length,
|
|
191
|
+
coolingDownAccounts: accounts.filter((a) => a.cooldownUntil > now).length,
|
|
177
192
|
};
|
|
178
193
|
}
|
|
179
194
|
// Force refresh cookies (for mass failures)
|
|
180
195
|
async function forceRefreshCookies() {
|
|
181
196
|
try {
|
|
182
|
-
(0, logger_1.log)(
|
|
197
|
+
(0, logger_1.log)("info", "cookiePool.forceRefresh", {});
|
|
183
198
|
const refreshed = await (0, cosiall_client_1.fetchCookiesFromCosiall)();
|
|
184
199
|
// Rebuild state (same logic as periodic refresh)
|
|
185
200
|
const newMap = new Map();
|
|
@@ -198,11 +213,11 @@ async function forceRefreshCookies() {
|
|
|
198
213
|
poolState.accounts = newMap;
|
|
199
214
|
poolState.order = newOrder;
|
|
200
215
|
poolState.consecutiveMassFailures = 0; // Reset counter
|
|
201
|
-
(0, logger_1.log)(
|
|
216
|
+
(0, logger_1.log)("info", "cookiePool.forceRefreshed", { count: refreshed.length });
|
|
202
217
|
}
|
|
203
218
|
catch (e) {
|
|
204
219
|
const err = e;
|
|
205
|
-
(0, logger_1.log)(
|
|
220
|
+
(0, logger_1.log)("error", "cookiePool.forceRefreshFailed", { error: err?.message });
|
|
206
221
|
throw e;
|
|
207
222
|
}
|
|
208
223
|
}
|
|
@@ -218,7 +233,9 @@ function getAccountForSession(sessionId) {
|
|
|
218
233
|
}
|
|
219
234
|
// Check if account is still healthy
|
|
220
235
|
const entry = poolState.accounts.get(session.accountId);
|
|
221
|
-
if (!entry ||
|
|
236
|
+
if (!entry ||
|
|
237
|
+
isExpired(entry) ||
|
|
238
|
+
entry.failures >= getSettings().maxFailures) {
|
|
222
239
|
sessionAccountMap.delete(sessionId);
|
|
223
240
|
return undefined;
|
|
224
241
|
}
|
|
@@ -228,13 +245,13 @@ function setAccountForSession(sessionId, accountId) {
|
|
|
228
245
|
sessionAccountMap.set(sessionId, {
|
|
229
246
|
accountId,
|
|
230
247
|
lastUsedAt: Date.now(),
|
|
231
|
-
expiresAt: Date.now() +
|
|
248
|
+
expiresAt: Date.now() + 30 * 60 * 1000, // 30 minutes
|
|
232
249
|
});
|
|
233
|
-
(0, logger_1.log)(
|
|
250
|
+
(0, logger_1.log)("debug", "cookiePool.sessionSticky", { sessionId, accountId });
|
|
234
251
|
}
|
|
235
252
|
function clearSessionAccount(sessionId) {
|
|
236
253
|
sessionAccountMap.delete(sessionId);
|
|
237
|
-
(0, logger_1.log)(
|
|
254
|
+
(0, logger_1.log)("debug", "cookiePool.sessionCleared", { sessionId });
|
|
238
255
|
}
|
|
239
256
|
// Modified selectAccountForRequest to support sessions (Phase 2.1)
|
|
240
257
|
async function selectAccountForRequest(sessionId) {
|
|
@@ -245,8 +262,14 @@ async function selectAccountForRequest(sessionId) {
|
|
|
245
262
|
const stickyAccountId = getAccountForSession(sessionId);
|
|
246
263
|
if (stickyAccountId) {
|
|
247
264
|
const entry = poolState.accounts.get(stickyAccountId);
|
|
248
|
-
if (entry &&
|
|
249
|
-
(
|
|
265
|
+
if (entry &&
|
|
266
|
+
!isExpired(entry) &&
|
|
267
|
+
entry.failures < settings.maxFailures &&
|
|
268
|
+
entry.cooldownUntil <= nowMs()) {
|
|
269
|
+
(0, logger_1.log)("debug", "cookiePool.sessionReuse", {
|
|
270
|
+
sessionId,
|
|
271
|
+
accountId: stickyAccountId,
|
|
272
|
+
});
|
|
250
273
|
entry.lastUsedAt = nowMs();
|
|
251
274
|
return { accountId: stickyAccountId, cookies: entry.cookies };
|
|
252
275
|
}
|
|
@@ -272,8 +295,12 @@ async function selectAccountForRequest(sessionId) {
|
|
|
272
295
|
continue;
|
|
273
296
|
// Select this account
|
|
274
297
|
poolState.rrIndex = (idx + 1) % n;
|
|
275
|
-
(0, logger_1.log)(
|
|
276
|
-
|
|
298
|
+
(0, logger_1.log)("debug", "cookiePool.select", {
|
|
299
|
+
accountId: entry.accountId,
|
|
300
|
+
rrIndex: poolState.rrIndex,
|
|
301
|
+
sessionId: sessionId || "none",
|
|
302
|
+
});
|
|
303
|
+
(0, metrics_1.incrementMetric)("accountSelections");
|
|
277
304
|
entry.lastUsedAt = nowMs();
|
|
278
305
|
// Set sticky session if sessionId provided
|
|
279
306
|
if (sessionId) {
|
|
@@ -281,7 +308,7 @@ async function selectAccountForRequest(sessionId) {
|
|
|
281
308
|
}
|
|
282
309
|
return { accountId: entry.accountId, cookies: entry.cookies };
|
|
283
310
|
}
|
|
284
|
-
throw new errors_1.LinkedInClientError(
|
|
311
|
+
throw new errors_1.LinkedInClientError("No valid LinkedIn accounts", "NO_VALID_ACCOUNTS", 503);
|
|
285
312
|
}
|
|
286
313
|
function getAccountsSummary() {
|
|
287
314
|
const now = nowMs();
|
|
@@ -314,9 +341,14 @@ function reportAccountFailure(accountId, isAuthError) {
|
|
|
314
341
|
if (isAuthError || entry.failures >= settings.maxFailures) {
|
|
315
342
|
entry.cooldownUntil = nowMs() + settings.cooldownMs;
|
|
316
343
|
}
|
|
317
|
-
(0, logger_1.log)(
|
|
344
|
+
(0, logger_1.log)("warn", "cookiePool.failure", {
|
|
345
|
+
accountId,
|
|
346
|
+
failures: entry.failures,
|
|
347
|
+
isAuthError,
|
|
348
|
+
cooldownUntil: entry.cooldownUntil,
|
|
349
|
+
});
|
|
318
350
|
if (isAuthError)
|
|
319
|
-
(0, metrics_1.incrementMetric)(
|
|
351
|
+
(0, metrics_1.incrementMetric)("authErrors");
|
|
320
352
|
}
|
|
321
353
|
function reportAccountSuccess(accountId) {
|
|
322
354
|
const entry = poolState.accounts.get(accountId);
|
|
@@ -324,16 +356,16 @@ function reportAccountSuccess(accountId) {
|
|
|
324
356
|
return;
|
|
325
357
|
entry.failures = 0;
|
|
326
358
|
entry.cooldownUntil = 0;
|
|
327
|
-
(0, logger_1.log)(
|
|
359
|
+
(0, logger_1.log)("debug", "cookiePool.success", { accountId });
|
|
328
360
|
}
|
|
329
361
|
function buildCookieHeader(cookies) {
|
|
330
|
-
return cookies.map(c => `${c.name}=${c.value}`).join(
|
|
362
|
+
return cookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
331
363
|
}
|
|
332
364
|
function extractCsrfToken(cookies) {
|
|
333
|
-
const jsession = cookies.find(c => c.name.toUpperCase() ===
|
|
365
|
+
const jsession = cookies.find((c) => c.name.toUpperCase() === "JSESSIONID");
|
|
334
366
|
if (!jsession || !jsession.value)
|
|
335
|
-
throw new Error(
|
|
336
|
-
return jsession.value.replace(/^\"|\"$/g,
|
|
367
|
+
throw new Error("Missing CSRF token");
|
|
368
|
+
return jsession.value.replace(/^\"|\"$/g, "");
|
|
337
369
|
}
|
|
338
370
|
function getSettings() {
|
|
339
371
|
try {
|
|
@@ -372,7 +404,7 @@ function adminResetAccount(accountId) {
|
|
|
372
404
|
*/
|
|
373
405
|
function _testGetAllAccountIds() {
|
|
374
406
|
if (!poolState.initialized) {
|
|
375
|
-
throw new Error(
|
|
407
|
+
throw new Error("TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.");
|
|
376
408
|
}
|
|
377
409
|
return Array.from(poolState.accounts.keys());
|
|
378
410
|
}
|
|
@@ -383,7 +415,7 @@ function _testGetAllAccountIds() {
|
|
|
383
415
|
*/
|
|
384
416
|
function _testGetAccountCookies(accountId) {
|
|
385
417
|
if (!poolState.initialized) {
|
|
386
|
-
throw new Error(
|
|
418
|
+
throw new Error("TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.");
|
|
387
419
|
}
|
|
388
420
|
return poolState.accounts.get(accountId)?.cookies;
|
|
389
421
|
}
|
|
@@ -394,7 +426,7 @@ function _testGetAccountCookies(accountId) {
|
|
|
394
426
|
*/
|
|
395
427
|
function _testGetAccountEntry(accountId) {
|
|
396
428
|
if (!poolState.initialized) {
|
|
397
|
-
throw new Error(
|
|
429
|
+
throw new Error("TEST: Cookie pool not initialized. Call initializeLinkedInClient() first.");
|
|
398
430
|
}
|
|
399
431
|
return poolState.accounts.get(accountId);
|
|
400
432
|
}
|
package/dist/cosiall-client.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
|
-
import type { AccountCookies } from
|
|
1
|
+
import type { AccountCookies } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches LinkedIn cookies for all available accounts from the Cosiall API.
|
|
4
|
+
* These cookies are used for authenticating requests to LinkedIn's API.
|
|
5
|
+
*
|
|
6
|
+
* @returns Array of AccountCookies, each containing accountId, cookies array, and optional expiresAt
|
|
7
|
+
* @throws LinkedInClientError with code REQUEST_FAILED if Cosiall API returns non-OK status
|
|
8
|
+
* @throws LinkedInClientError with code REQUEST_FAILED if response format is invalid
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* - Reports failures to Sentry if configured
|
|
12
|
+
* - Validates each account has required cookie structure
|
|
13
|
+
* - Filters out malformed account entries
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const accounts = await fetchCookiesFromCosiall();
|
|
18
|
+
* console.log(`Loaded ${accounts.length} LinkedIn accounts`);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
2
21
|
export declare function fetchCookiesFromCosiall(): Promise<AccountCookies[]>;
|
package/dist/cosiall-client.js
CHANGED
|
@@ -38,69 +38,92 @@ const config_1 = require("./config");
|
|
|
38
38
|
const errors_1 = require("./utils/errors");
|
|
39
39
|
const logger_1 = require("./utils/logger");
|
|
40
40
|
const metrics_1 = require("./utils/metrics");
|
|
41
|
+
/**
|
|
42
|
+
* Fetches LinkedIn cookies for all available accounts from the Cosiall API.
|
|
43
|
+
* These cookies are used for authenticating requests to LinkedIn's API.
|
|
44
|
+
*
|
|
45
|
+
* @returns Array of AccountCookies, each containing accountId, cookies array, and optional expiresAt
|
|
46
|
+
* @throws LinkedInClientError with code REQUEST_FAILED if Cosiall API returns non-OK status
|
|
47
|
+
* @throws LinkedInClientError with code REQUEST_FAILED if response format is invalid
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* - Reports failures to Sentry if configured
|
|
51
|
+
* - Validates each account has required cookie structure
|
|
52
|
+
* - Filters out malformed account entries
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const accounts = await fetchCookiesFromCosiall();
|
|
57
|
+
* console.log(`Loaded ${accounts.length} LinkedIn accounts`);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
41
60
|
async function fetchCookiesFromCosiall() {
|
|
42
61
|
const { cosiallApiUrl, cosiallApiKey } = (0, config_1.getConfig)();
|
|
43
|
-
const base = cosiallApiUrl.replace(/\/+$/,
|
|
62
|
+
const base = cosiallApiUrl.replace(/\/+$/, "");
|
|
44
63
|
const url = `${base}/api/flexiq/linkedin-cookies/all`;
|
|
45
|
-
(0, logger_1.log)(
|
|
46
|
-
(0, metrics_1.incrementMetric)(
|
|
64
|
+
(0, logger_1.log)("info", "cosiall.fetch.start", { url });
|
|
65
|
+
(0, metrics_1.incrementMetric)("cosiallFetches");
|
|
47
66
|
const response = await fetch(url, {
|
|
48
|
-
method:
|
|
67
|
+
method: "GET",
|
|
49
68
|
headers: {
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
"X-API-Key": cosiallApiKey,
|
|
70
|
+
Accept: "application/json",
|
|
52
71
|
},
|
|
53
72
|
});
|
|
54
73
|
if (!response.ok) {
|
|
55
|
-
(0, logger_1.log)(
|
|
56
|
-
(0, metrics_1.incrementMetric)(
|
|
74
|
+
(0, logger_1.log)("warn", "cosiall.fetch.error", { status: response.status });
|
|
75
|
+
(0, metrics_1.incrementMetric)("cosiallFailures");
|
|
57
76
|
// Report Cosiall downtime to Sentry (critical for operations)
|
|
58
77
|
try {
|
|
59
|
-
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require(
|
|
60
|
-
reportCriticalError(
|
|
78
|
+
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
79
|
+
reportCriticalError("Cosiall API failure - cookie service unavailable", {
|
|
61
80
|
status: response.status,
|
|
62
81
|
url,
|
|
63
|
-
tags: { component:
|
|
82
|
+
tags: { component: "cosiall-client", severity: "critical" },
|
|
64
83
|
});
|
|
65
84
|
}
|
|
66
85
|
catch { }
|
|
67
|
-
throw new errors_1.LinkedInClientError(
|
|
86
|
+
throw new errors_1.LinkedInClientError("Cosiall fetch failed", "REQUEST_FAILED", response.status);
|
|
68
87
|
}
|
|
69
88
|
const data = await response.json();
|
|
70
89
|
if (!Array.isArray(data)) {
|
|
71
|
-
(0, logger_1.log)(
|
|
90
|
+
(0, logger_1.log)("error", "cosiall.fetch.invalidFormat", { dataType: typeof data });
|
|
72
91
|
// Report data format issues to Sentry
|
|
73
92
|
try {
|
|
74
|
-
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require(
|
|
75
|
-
reportCriticalError(
|
|
76
|
-
expectedType:
|
|
93
|
+
const { reportCriticalError } = await Promise.resolve().then(() => __importStar(require("./utils/sentry")));
|
|
94
|
+
reportCriticalError("Cosiall API returned invalid format", {
|
|
95
|
+
expectedType: "array",
|
|
77
96
|
actualType: typeof data,
|
|
78
|
-
tags: { component:
|
|
97
|
+
tags: { component: "cosiall-client", severity: "critical" },
|
|
79
98
|
});
|
|
80
99
|
}
|
|
81
100
|
catch { }
|
|
82
|
-
throw new errors_1.LinkedInClientError(
|
|
101
|
+
throw new errors_1.LinkedInClientError("Invalid Cosiall response format", "REQUEST_FAILED", 500);
|
|
83
102
|
}
|
|
84
|
-
(0, logger_1.log)(
|
|
85
|
-
(0, metrics_1.incrementMetric)(
|
|
103
|
+
(0, logger_1.log)("info", "cosiall.fetch.success", { count: data.length });
|
|
104
|
+
(0, metrics_1.incrementMetric)("cosiallSuccess");
|
|
86
105
|
function isCookie(obj) {
|
|
87
|
-
return !!obj && typeof obj ===
|
|
106
|
+
return !!obj && typeof obj === "object" && "name" in obj && "value" in obj;
|
|
88
107
|
}
|
|
89
108
|
function isItem(obj) {
|
|
90
|
-
if (!obj || typeof obj !==
|
|
109
|
+
if (!obj || typeof obj !== "object")
|
|
91
110
|
return false;
|
|
92
111
|
const rec = obj;
|
|
93
|
-
if (typeof rec.accountId !==
|
|
112
|
+
if (typeof rec.accountId !== "string")
|
|
94
113
|
return false;
|
|
95
114
|
if (!Array.isArray(rec.cookies))
|
|
96
115
|
return false;
|
|
97
116
|
if (!rec.cookies.every(isCookie))
|
|
98
117
|
return false;
|
|
99
|
-
if (rec.expiresAt !== undefined && typeof rec.expiresAt !==
|
|
118
|
+
if (rec.expiresAt !== undefined && typeof rec.expiresAt !== "number")
|
|
100
119
|
return false;
|
|
101
120
|
return true;
|
|
102
121
|
}
|
|
103
122
|
return data
|
|
104
123
|
.filter(isItem)
|
|
105
|
-
.map((item) => ({
|
|
124
|
+
.map((item) => ({
|
|
125
|
+
accountId: item.accountId,
|
|
126
|
+
cookies: item.cookies,
|
|
127
|
+
expiresAt: item.expiresAt,
|
|
128
|
+
}));
|
|
106
129
|
}
|