coffeeinabit 0.0.52 → 0.0.54
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/helpers/auto_start.js +14 -2
- package/linkedin_automation.js +173 -86
- package/package.json +1 -1
- package/public/dashboard.html +85 -9
- package/routes/auth.js +29 -2
- package/routes/automation.js +52 -2
- package/test/browser_runner.js +39 -0
- package/test/browser_server.js +107 -0
- package/test/debug_notifications_page.js +55 -0
- package/test/debug_snapshots/step4_type_message_1770142927499.html +13027 -0
- package/test/debug_snapshots/step4_type_message_1770142927499.png +0 -0
- package/test/debug_snapshots/step4_type_message_1770142967238.html +13064 -0
- package/test/debug_snapshots/step4_type_message_1770142967238.png +0 -0
- package/test/debug_snapshots/step4_type_message_1770142967238_analysis.txt +23 -0
- package/test/debug_snapshots/step4_type_message_1770142967238_error.txt +27 -0
- package/test/notifications_debug.html +4025 -0
- package/test/notifications_debug.png +0 -0
- package/test/send_linkedin_message.js +168 -0
- package/test/test_get_message_threads.js +498 -0
- package/test/test_get_new_engagers.js +154 -0
- package/test/test_message_simple.js +225 -0
- package/test/test_output_message_threads.json +5410 -0
- package/test/test_send_message.js +559 -0
- package/tools/get_message_threads.js +392 -0
- package/tools/get_new_engagers.js +266 -0
- package/tools/get_profile.js +14 -1
- package/tools/human_typing.js +41 -14
- package/tools/send_linkedin_message.js +170 -0
- package/tools/send_messages.js +16 -131
package/helpers/auto_start.js
CHANGED
|
@@ -129,10 +129,22 @@ async function tryFileBasedAutoStart(contextDir, cloudAuth, linkedinAutomation)
|
|
|
129
129
|
*/
|
|
130
130
|
export async function tryStartAutomationWithSession(sessionData, cloudAuth, linkedinAutomation) {
|
|
131
131
|
try {
|
|
132
|
+
const newUserEmail = sessionData.user?.email || 'default_user';
|
|
133
|
+
|
|
132
134
|
// Check if automation is already running
|
|
133
135
|
if (linkedinAutomation.isRunning) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
// If running for the same user, skip (already running)
|
|
137
|
+
if (linkedinAutomation._currentUserEmail === newUserEmail) {
|
|
138
|
+
console.log('[AutoStart] Automation is already running for this user, skipping auto-start');
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
// If running for a different user, stop it first
|
|
142
|
+
console.log(`[AutoStart] Automation running for different user (${linkedinAutomation._currentUserEmail}), stopping...`);
|
|
143
|
+
try {
|
|
144
|
+
await linkedinAutomation.stopAutomation();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error('[AutoStart] Error stopping previous automation:', err.message);
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
// Create a session-like object that works with cloudAuth methods
|
package/linkedin_automation.js
CHANGED
|
@@ -14,6 +14,8 @@ import { executeGetMessages, closeAllConversationBubbles } from './tools/get_mes
|
|
|
14
14
|
import { executeLinkedInSearch } from './tools/get_linkedin_search_results.js';
|
|
15
15
|
import { executeGetNewMessages } from './tools/get_new_messages.js';
|
|
16
16
|
import { executeGetLinkedInUpdates } from './tools/get_linkedin_updates.js';
|
|
17
|
+
import { executeGetNewEngagers } from './tools/get_new_engagers.js';
|
|
18
|
+
import { executeGetMessageThreads } from './tools/get_message_threads.js';
|
|
17
19
|
import { safeGoto } from './tools/navigation.js';
|
|
18
20
|
import { humanLikeAmbientMove, resetHumanMouseState } from './tools/human_mouse.js';
|
|
19
21
|
import { logger } from './helpers/logger.js';
|
|
@@ -149,19 +151,24 @@ export class LinkedInAutomation {
|
|
|
149
151
|
if (this.hasActiveBrowser()) {
|
|
150
152
|
return true;
|
|
151
153
|
}
|
|
152
|
-
|
|
154
|
+
// Only call handleBrowserUnavailable if not already in that state
|
|
155
|
+
if (this.status !== 'browser_unavailable') {
|
|
156
|
+
this.handleBrowserUnavailable(context);
|
|
157
|
+
}
|
|
153
158
|
return false;
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
handleBrowserUnavailable(context) {
|
|
162
|
+
// Only log and stop once - avoid spam when multiple intervals detect browser gone
|
|
163
|
+
if (this.status === 'browser_unavailable') {
|
|
164
|
+
return; // Already handled
|
|
165
|
+
}
|
|
157
166
|
logger.warn(`[LinkedInAutomation] Browser unavailable during ${context}. Stopping background activities...`);
|
|
167
|
+
this.status = 'browser_unavailable';
|
|
158
168
|
this.stopActionPolling();
|
|
159
169
|
this.stopScreenshotCapture();
|
|
160
170
|
this.stopAmbientMouseMovements();
|
|
161
|
-
|
|
162
|
-
this.status = 'browser_unavailable';
|
|
163
|
-
this.emitStatus();
|
|
164
|
-
}
|
|
171
|
+
this.emitStatus();
|
|
165
172
|
}
|
|
166
173
|
|
|
167
174
|
getLinkedInSessionPath() {
|
|
@@ -298,8 +305,17 @@ export class LinkedInAutomation {
|
|
|
298
305
|
|
|
299
306
|
async startAutomation(headless = true) {
|
|
300
307
|
try {
|
|
308
|
+
// Don't restart if already running or in authenticating phase (user trying to login)
|
|
309
|
+
if (this.hasActiveBrowser() &&
|
|
310
|
+
(this.currentPhase === this.PHASE_AUTHENTICATING ||
|
|
311
|
+
this.currentPhase === this.PHASE_AUTHENTICATED ||
|
|
312
|
+
this.currentPhase === this.PHASE_RUNNING)) {
|
|
313
|
+
logger.log(`[LinkedInAutomation] Browser already active in phase ${this.currentPhase}, skipping restart`);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
301
317
|
logger.log('[LinkedInAutomation] Starting automation...');
|
|
302
|
-
|
|
318
|
+
|
|
303
319
|
// Reset authentication promise
|
|
304
320
|
this.authenticationPromise = new Promise((resolve, reject) => {
|
|
305
321
|
this.authenticationResolve = resolve;
|
|
@@ -397,9 +413,9 @@ export class LinkedInAutomation {
|
|
|
397
413
|
this.needsManualLogin = true;
|
|
398
414
|
this.status = 'manual_login_needed';
|
|
399
415
|
this.emitStatus();
|
|
400
|
-
|
|
416
|
+
|
|
401
417
|
await this.closeBrowser();
|
|
402
|
-
|
|
418
|
+
|
|
403
419
|
this.headless = false;
|
|
404
420
|
await this.launchBrowserAndLogin();
|
|
405
421
|
|
|
@@ -448,8 +464,9 @@ export class LinkedInAutomation {
|
|
|
448
464
|
|
|
449
465
|
const pollInterval = setInterval(async () => {
|
|
450
466
|
try {
|
|
451
|
-
if
|
|
452
|
-
|
|
467
|
+
// Only check if page exists, not isRunning (since isRunning is false during authentication)
|
|
468
|
+
if (!this.page) {
|
|
469
|
+
logger.log('[LinkedInAutomation] Login polling stopped - no page available');
|
|
453
470
|
clearInterval(pollInterval);
|
|
454
471
|
return;
|
|
455
472
|
}
|
|
@@ -474,28 +491,22 @@ export class LinkedInAutomation {
|
|
|
474
491
|
const secondCheck = this.page.url();
|
|
475
492
|
if (secondCheck.includes('/feed')) {
|
|
476
493
|
logger.log('[LinkedInAutomation] Second check confirmed - still on /feed/');
|
|
477
|
-
|
|
494
|
+
|
|
478
495
|
logger.log('[LinkedInAutomation] Session will be persisted automatically by launchPersistentContext...');
|
|
479
|
-
|
|
496
|
+
|
|
497
|
+
// Also save cookies explicitly for redundancy
|
|
498
|
+
logger.log('[LinkedInAutomation] Saving cookies explicitly after successful login...');
|
|
499
|
+
await this.saveLinkedInSession();
|
|
500
|
+
|
|
480
501
|
this.status = 'linkedin_logged_in';
|
|
481
502
|
this.emitStatus();
|
|
482
503
|
|
|
483
504
|
if (this.originalHeadless && this.needsManualLogin) {
|
|
484
|
-
logger.log('[LinkedInAutomation] Manual login completed. Waiting for session to
|
|
505
|
+
logger.log('[LinkedInAutomation] Manual login completed. Waiting for session to persist...');
|
|
485
506
|
clearInterval(pollInterval);
|
|
486
|
-
|
|
507
|
+
|
|
487
508
|
await this.waitRandom(5000, 7000);
|
|
488
509
|
|
|
489
|
-
const storageStatePath = this.getStorageStatePath();
|
|
490
|
-
try {
|
|
491
|
-
await this.context.storageState({ path: storageStatePath });
|
|
492
|
-
const cookies = await this.context.cookies();
|
|
493
|
-
logger.log(`[LinkedInAutomation] Saved storage state with ${cookies.length} cookies to: ${storageStatePath}`);
|
|
494
|
-
} catch (error) {
|
|
495
|
-
logger.error('[LinkedInAutomation] Error saving storage state:', error.message);
|
|
496
|
-
}
|
|
497
|
-
await this.waitRandom(3000, 5000);
|
|
498
|
-
|
|
499
510
|
logger.log('[LinkedInAutomation] Switching back to headless mode...');
|
|
500
511
|
this.status = 'switching_to_headless';
|
|
501
512
|
this.emitStatus();
|
|
@@ -547,52 +558,75 @@ export class LinkedInAutomation {
|
|
|
547
558
|
try {
|
|
548
559
|
logger.log('[LinkedInAutomation] Closing browser...');
|
|
549
560
|
this.stopAmbientMouseMovements();
|
|
550
|
-
|
|
561
|
+
|
|
551
562
|
if (this.page) {
|
|
552
|
-
|
|
563
|
+
try {
|
|
564
|
+
await this.page.close();
|
|
565
|
+
} catch (e) {
|
|
566
|
+
logger.warn('[LinkedInAutomation] Page already closed:', e.message);
|
|
567
|
+
}
|
|
553
568
|
this.page = null;
|
|
554
569
|
}
|
|
555
570
|
|
|
556
571
|
if (this.context) {
|
|
572
|
+
// Persistent context auto-saves cookies to userDataPath on close.
|
|
573
|
+
// No need to save a separate storage state file.
|
|
557
574
|
try {
|
|
558
|
-
const storageStatePath = this.getStorageStatePath();
|
|
559
|
-
const cookies = await this.context.cookies();
|
|
560
|
-
if (cookies.length > 0) {
|
|
561
|
-
await this.context.storageState({ path: storageStatePath });
|
|
562
|
-
logger.log(`[LinkedInAutomation] Saved storage state with ${cookies.length} cookies to: ${storageStatePath}`);
|
|
563
|
-
}
|
|
564
575
|
await this.context.close();
|
|
565
576
|
await this.waitRandom(3000, 5000);
|
|
566
577
|
} catch (error) {
|
|
567
|
-
logger.error('[LinkedInAutomation] Error
|
|
568
|
-
try {
|
|
569
|
-
await this.context.close();
|
|
570
|
-
await this.waitRandom(3000, 5000);
|
|
571
|
-
} catch (closeError) {
|
|
572
|
-
logger.error('[LinkedInAutomation] Error closing context:', closeError.message);
|
|
573
|
-
}
|
|
578
|
+
logger.error('[LinkedInAutomation] Error closing context:', error.message);
|
|
574
579
|
}
|
|
575
580
|
this.context = null;
|
|
576
581
|
this.browser = null;
|
|
577
582
|
}
|
|
578
583
|
|
|
584
|
+
// Clean up stale Chromium lock files from the user data directory.
|
|
585
|
+
// If a previous Chromium process was killed (OOM, SIGTRAP, etc.), these
|
|
586
|
+
// symlinks remain and prevent a new instance from launching.
|
|
587
|
+
this._cleanupBrowserLockFiles();
|
|
588
|
+
|
|
579
589
|
logger.log('[LinkedInAutomation] Browser closed successfully');
|
|
580
590
|
} catch (error) {
|
|
581
591
|
logger.error('[LinkedInAutomation] Error closing browser:', error);
|
|
582
592
|
}
|
|
583
593
|
}
|
|
584
594
|
|
|
595
|
+
_cleanupBrowserLockFiles() {
|
|
596
|
+
try {
|
|
597
|
+
const sanitizedEmail = (this.userEmail || 'default_user').replace(/[^a-zA-Z0-9]/g, '_');
|
|
598
|
+
const userDataPath = path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}`);
|
|
599
|
+
const lockFiles = ['SingletonLock', 'SingletonSocket', 'SingletonCookie'];
|
|
600
|
+
for (const lockFile of lockFiles) {
|
|
601
|
+
const lockPath = path.join(userDataPath, lockFile);
|
|
602
|
+
if (fs.existsSync(lockPath)) {
|
|
603
|
+
fs.unlinkSync(lockPath);
|
|
604
|
+
logger.log(`[LinkedInAutomation] Removed stale lock file: ${lockFile}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} catch (error) {
|
|
608
|
+
logger.warn('[LinkedInAutomation] Error cleaning up lock files:', error.message);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
585
612
|
async stopAutomation() {
|
|
586
613
|
try {
|
|
587
614
|
logger.log('[LinkedInAutomation] Stopping automation...');
|
|
588
615
|
this.transitionToPhase(this.PHASE_STOPPING);
|
|
589
616
|
this.isRunning = false;
|
|
590
617
|
|
|
591
|
-
// Reject any pending authentication promises
|
|
618
|
+
// Reject any pending authentication promises (defer so stop route can complete first)
|
|
592
619
|
if (this.authenticationReject) {
|
|
593
|
-
this.authenticationReject
|
|
620
|
+
const reject = this.authenticationReject;
|
|
594
621
|
this.authenticationResolve = null;
|
|
595
622
|
this.authenticationReject = null;
|
|
623
|
+
setImmediate(() => {
|
|
624
|
+
try {
|
|
625
|
+
reject(new Error('Automation stopped'));
|
|
626
|
+
} catch (_) {
|
|
627
|
+
// Consumer may have already settled; ignore
|
|
628
|
+
}
|
|
629
|
+
});
|
|
596
630
|
}
|
|
597
631
|
|
|
598
632
|
this.stopScreenshotCapture();
|
|
@@ -701,11 +735,19 @@ export class LinkedInAutomation {
|
|
|
701
735
|
let currentInterval = this.pollIntervalMs;
|
|
702
736
|
|
|
703
737
|
const pollWithBackoff = async () => {
|
|
738
|
+
// Check if polling should continue
|
|
704
739
|
if (!this.isActionPollingActive) {
|
|
705
740
|
logger.log('[LinkedInAutomation] Action polling stopped, not scheduling next poll');
|
|
706
741
|
return;
|
|
707
742
|
}
|
|
708
743
|
|
|
744
|
+
// Check if browser is still available before polling
|
|
745
|
+
if (!this.hasActiveBrowser()) {
|
|
746
|
+
logger.log('[LinkedInAutomation] Browser unavailable, stopping action polling loop');
|
|
747
|
+
this.isActionPollingActive = false;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
709
751
|
if (this.currentAction) {
|
|
710
752
|
logger.log('[LinkedInAutomation] Action in progress, skipping polling...');
|
|
711
753
|
logger.log('[LinkedInAutomation] Next poll in', Math.round(currentInterval / 1000), 'seconds');
|
|
@@ -716,6 +758,13 @@ export class LinkedInAutomation {
|
|
|
716
758
|
// pollForActions now returns true if actions were found and processed
|
|
717
759
|
const foundActions = await this.pollForActions();
|
|
718
760
|
|
|
761
|
+
// Re-check after potentially long-running action execution (which includes task polling)
|
|
762
|
+
if (!this.isActionPollingActive || !this.hasActiveBrowser()) {
|
|
763
|
+
logger.log('[LinkedInAutomation] Polling stopped or browser closed during action, not scheduling next poll');
|
|
764
|
+
this.isActionPollingActive = false;
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
719
768
|
if (foundActions) {
|
|
720
769
|
// reset backoff when we processed work
|
|
721
770
|
currentInterval = this.pollIntervalMs;
|
|
@@ -733,6 +782,9 @@ export class LinkedInAutomation {
|
|
|
733
782
|
}
|
|
734
783
|
|
|
735
784
|
stopActionPolling() {
|
|
785
|
+
if (!this.isActionPollingActive) {
|
|
786
|
+
return; // Already stopped
|
|
787
|
+
}
|
|
736
788
|
logger.log('[LinkedInAutomation] Action polling stopped');
|
|
737
789
|
this.isActionPollingActive = false;
|
|
738
790
|
}
|
|
@@ -956,7 +1008,14 @@ export class LinkedInAutomation {
|
|
|
956
1008
|
this.emitStatus();
|
|
957
1009
|
|
|
958
1010
|
let result = null;
|
|
959
|
-
|
|
1011
|
+
|
|
1012
|
+
// Normalize: if no username in parameters, use linkedin_profile_id from action
|
|
1013
|
+
if (!action.parameters?.username && action.linkedin_profile_id) {
|
|
1014
|
+
action.parameters = action.parameters || {};
|
|
1015
|
+
action.parameters.username = action.linkedin_profile_id;
|
|
1016
|
+
logger.log('[LinkedInAutomation] Set username from linkedin_profile_id:', action.linkedin_profile_id);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
960
1019
|
// add esc to close any modals that might interfere
|
|
961
1020
|
await this.page.keyboard.press('Escape');
|
|
962
1021
|
await this.waitRandom(100, 2000);
|
|
@@ -998,6 +1057,14 @@ export class LinkedInAutomation {
|
|
|
998
1057
|
logger.log('[LinkedInAutomation] Executing get_linkedin_updates action...');
|
|
999
1058
|
result = await executeGetLinkedInUpdates(this.page, action, await this.getAccessToken());
|
|
1000
1059
|
break;
|
|
1060
|
+
case 'get_new_engagers':
|
|
1061
|
+
logger.log('[LinkedInAutomation] Executing get_new_engagers action...');
|
|
1062
|
+
result = await executeGetNewEngagers(this.page, action);
|
|
1063
|
+
break;
|
|
1064
|
+
case 'get_message_threads':
|
|
1065
|
+
logger.log('[LinkedInAutomation] Executing get_message_threads action...');
|
|
1066
|
+
result = await executeGetMessageThreads(this.page, action);
|
|
1067
|
+
break;
|
|
1001
1068
|
default:
|
|
1002
1069
|
logger.log('[LinkedInAutomation] Unknown action type:', action.action);
|
|
1003
1070
|
result = { error: `Unknown action type: ${action.action}` };
|
|
@@ -1316,14 +1383,51 @@ export class LinkedInAutomation {
|
|
|
1316
1383
|
return path.join(this.linkedInSessionDir, `storage_state_${sanitizedEmail}.json`);
|
|
1317
1384
|
}
|
|
1318
1385
|
|
|
1386
|
+
async clearCookies() {
|
|
1387
|
+
const email = this._currentUserEmail || this.userEmail || 'default_user';
|
|
1388
|
+
const sanitizedEmail = email.replace(/[^a-zA-Z0-9]/g, '_');
|
|
1389
|
+
const userDataPath = path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}`);
|
|
1390
|
+
const storageStatePath = this.getStorageStatePath();
|
|
1391
|
+
|
|
1392
|
+
// Stop automation if running
|
|
1393
|
+
if (this.isRunning) {
|
|
1394
|
+
logger.log('[LinkedInAutomation] Stopping automation before clearing cookies...');
|
|
1395
|
+
await this.stopAutomation();
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Delete the persistent context user data directory
|
|
1399
|
+
if (fs.existsSync(userDataPath)) {
|
|
1400
|
+
fs.rmSync(userDataPath, { recursive: true, force: true });
|
|
1401
|
+
logger.log(`[LinkedInAutomation] Deleted user data directory: ${userDataPath}`);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Delete the storage state file if it exists
|
|
1405
|
+
if (fs.existsSync(storageStatePath)) {
|
|
1406
|
+
fs.unlinkSync(storageStatePath);
|
|
1407
|
+
logger.log(`[LinkedInAutomation] Deleted storage state file: ${storageStatePath}`);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
logger.log(`[LinkedInAutomation] All cookies cleared for ${email}`);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1319
1413
|
async launchBrowserAndLogin() {
|
|
1320
1414
|
logger.log('[LinkedInAutomation] Launching Chromium with persistent context for LinkedIn login...');
|
|
1321
1415
|
|
|
1416
|
+
// Close any existing browser BEFORE updating userEmail (so cookies save to correct user's file)
|
|
1417
|
+
if (this.context || this.browser) {
|
|
1418
|
+
logger.log(`[LinkedInAutomation] Existing browser detected for user ${this.userEmail}, closing before re-launch...`);
|
|
1419
|
+
await this.closeBrowser(); // Uses old this.userEmail for storage path
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Now safe to update userEmail to the new user
|
|
1423
|
+
const previousUserEmail = this.userEmail;
|
|
1322
1424
|
this.userEmail = this._currentUserEmail || 'default_user';
|
|
1425
|
+
if (previousUserEmail && previousUserEmail !== this.userEmail && previousUserEmail !== 'default_user') {
|
|
1426
|
+
logger.log(`[LinkedInAutomation] User switched: ${previousUserEmail} -> ${this.userEmail}`);
|
|
1427
|
+
}
|
|
1323
1428
|
const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
|
|
1324
1429
|
const userDataPath = path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}`);
|
|
1325
|
-
|
|
1326
|
-
|
|
1430
|
+
|
|
1327
1431
|
logger.log('[LinkedInAutomation] User data path:', userDataPath);
|
|
1328
1432
|
|
|
1329
1433
|
if (!fs.existsSync(userDataPath)) {
|
|
@@ -1331,6 +1435,9 @@ export class LinkedInAutomation {
|
|
|
1331
1435
|
logger.log('[LinkedInAutomation] Created user data directory:', userDataPath);
|
|
1332
1436
|
}
|
|
1333
1437
|
|
|
1438
|
+
// Clean up stale lock files before launching (from previous crashes/OOM kills)
|
|
1439
|
+
this._cleanupBrowserLockFiles();
|
|
1440
|
+
|
|
1334
1441
|
const launchOptions = {
|
|
1335
1442
|
headless: this.headless || false,
|
|
1336
1443
|
viewport: (this.headless || false) ? { width: 1920, height: 1080 } : null,
|
|
@@ -1352,21 +1459,23 @@ export class LinkedInAutomation {
|
|
|
1352
1459
|
await this.enablePerformanceMode();
|
|
1353
1460
|
await this.preparePrimaryPage();
|
|
1354
1461
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1462
|
+
// Persistent context already manages its own cookies via userDataPath.
|
|
1463
|
+
// Do NOT load extra cookies from storage state file — it causes mixing/conflicts.
|
|
1464
|
+
|
|
1465
|
+
const cookiesAfterLaunch = await this.context.cookies();
|
|
1466
|
+
logger.log(`[LinkedInAutomation] Total ${cookiesAfterLaunch.length} cookies in persistent context`);
|
|
1467
|
+
|
|
1468
|
+
// If persistent context has no/few cookies, try loading from JSON backup
|
|
1469
|
+
if (cookiesAfterLaunch.length < 5) {
|
|
1470
|
+
logger.log('[LinkedInAutomation] Persistent context has few/no cookies, attempting to load from JSON backup...');
|
|
1471
|
+
const loaded = await this.loadLinkedInSession();
|
|
1472
|
+
if (loaded) {
|
|
1473
|
+
const cookiesAfterLoad = await this.context.cookies();
|
|
1474
|
+
logger.log(`[LinkedInAutomation] Loaded cookies from JSON backup, now have ${cookiesAfterLoad.length} cookies`);
|
|
1475
|
+
} else {
|
|
1476
|
+
logger.log('[LinkedInAutomation] No JSON backup found or failed to load');
|
|
1365
1477
|
}
|
|
1366
1478
|
}
|
|
1367
|
-
|
|
1368
|
-
const cookiesAfterLaunch = await this.context.cookies();
|
|
1369
|
-
logger.log(`[LinkedInAutomation] Total ${cookiesAfterLaunch.length} cookies in context`);
|
|
1370
1479
|
|
|
1371
1480
|
// await this.installCursorHighlight();
|
|
1372
1481
|
|
|
@@ -1433,21 +1542,13 @@ export class LinkedInAutomation {
|
|
|
1433
1542
|
const currentTitle = await this.page.title();
|
|
1434
1543
|
const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
|
|
1435
1544
|
const userDataPath = path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}`);
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
try {
|
|
1439
|
-
await this.context.storageState({ path: storageStatePath });
|
|
1440
|
-
const cookies = await this.context.cookies();
|
|
1441
|
-
logger.log(`[LinkedInAutomation] Saved storage state with ${cookies.length} cookies before mode switch`);
|
|
1442
|
-
} catch (error) {
|
|
1443
|
-
logger.error('[LinkedInAutomation] Error saving storage state:', error);
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1545
|
+
|
|
1546
|
+
// Persistent context auto-saves cookies to userDataPath on close.
|
|
1446
1547
|
await this.context.close();
|
|
1447
1548
|
await this.waitRandom(3000, 5000);
|
|
1448
|
-
|
|
1549
|
+
|
|
1449
1550
|
this.headless = newHeadless;
|
|
1450
|
-
|
|
1551
|
+
|
|
1451
1552
|
const launchOptions = {
|
|
1452
1553
|
headless: this.headless,
|
|
1453
1554
|
viewport: null,
|
|
@@ -1459,20 +1560,8 @@ export class LinkedInAutomation {
|
|
|
1459
1560
|
'--disable-setuid-sandbox'
|
|
1460
1561
|
]
|
|
1461
1562
|
};
|
|
1462
|
-
|
|
1563
|
+
|
|
1463
1564
|
this.context = await chromium.launchPersistentContext(userDataPath, launchOptions);
|
|
1464
|
-
|
|
1465
|
-
if (fs.existsSync(storageStatePath)) {
|
|
1466
|
-
try {
|
|
1467
|
-
const storageState = JSON.parse(fs.readFileSync(storageStatePath, 'utf8'));
|
|
1468
|
-
if (storageState.cookies && storageState.cookies.length > 0) {
|
|
1469
|
-
await this.context.addCookies(storageState.cookies);
|
|
1470
|
-
logger.log(`[LinkedInAutomation] Added ${storageState.cookies.length} cookies from storage state for mode switch`);
|
|
1471
|
-
}
|
|
1472
|
-
} catch (error) {
|
|
1473
|
-
logger.error('[LinkedInAutomation] Error loading storage state for mode switch:', error.message);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
1565
|
|
|
1477
1566
|
this.browser = this.context;
|
|
1478
1567
|
|
|
@@ -1539,13 +1628,11 @@ export class LinkedInAutomation {
|
|
|
1539
1628
|
verifyAttempts++;
|
|
1540
1629
|
}
|
|
1541
1630
|
|
|
1542
|
-
logger.log('[LinkedInAutomation] Session verification failed - redirected to login.
|
|
1543
|
-
await this.startLinkedInLogin();
|
|
1631
|
+
logger.log('[LinkedInAutomation] Session verification failed - redirected to login.');
|
|
1544
1632
|
return false;
|
|
1545
1633
|
} catch (error) {
|
|
1546
1634
|
logger.error('[LinkedInAutomation] Error verifying session:', error);
|
|
1547
|
-
logger.log('[LinkedInAutomation]
|
|
1548
|
-
await this.startLinkedInLogin();
|
|
1635
|
+
logger.log('[LinkedInAutomation] Session check failed, will need fresh login.');
|
|
1549
1636
|
return false;
|
|
1550
1637
|
}
|
|
1551
1638
|
}
|