coffeeinabit 0.0.1

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.
@@ -0,0 +1,784 @@
1
+ import { firefox } from 'playwright';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { humanLikeType, readLinkedInCredentials } from './tools/human_typing.js';
6
+ import { executeGetProfile } from './tools/get_profile.js';
7
+ import { executeLikePost } from './tools/like_post.js';
8
+ import { executeSendConnectionRequest } from './tools/send_connection_request.js';
9
+ import { executeSendMessages } from './tools/send_messages.js';
10
+ import { executeCommentPost } from './tools/comment_post.js';
11
+ import { executeGetMessages } from './tools/get_messages.js';
12
+ import { executeCheckConnectionStatus } from './tools/check_connection_status.js';
13
+ import { executeLinkedInSearch } from './tools/get_linkedin_search_results.js';
14
+ import { executeGetDailyLinkedInConnections } from './tools/get_daily_linkedin_connections.js';
15
+ import { executeGetNewMessages } from './tools/get_new_messages.js';
16
+ import { executeGetLinkedInUpdates } from './tools/get_linkedin_updates.js';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+
21
+ export class LinkedInAutomation {
22
+ constructor(io) {
23
+ this.io = io;
24
+ this.browser = null;
25
+ this.context = null;
26
+ this.page = null;
27
+ this.isRunning = false;
28
+ this.status = 'idle';
29
+ this.currentUrl = '';
30
+ this.title = '';
31
+ this.screenshotInterval = null;
32
+ this.actionPollingInterval = null;
33
+ this.screenshotIntervalMs = 3000;
34
+ this.actionPollingIntervalMs = 15000;
35
+ this.currentAction = null;
36
+ this.userEmail = null;
37
+ this.isActionPollingActive = false;
38
+ this.linkedInSessionDir = path.join(__dirname, 'context');
39
+ }
40
+
41
+ getLinkedInSessionPath() {
42
+ const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
43
+ return path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}.json`);
44
+ }
45
+
46
+ async saveLinkedInSession() {
47
+ try {
48
+ if (!this.context || !this.page) {
49
+ console.error('[LinkedInAutomation] Cannot save session - no context or page');
50
+ return false;
51
+ }
52
+
53
+ console.log('[LinkedInAutomation] Saving LinkedIn session...');
54
+
55
+ const cookies = await this.context.cookies();
56
+ const localStorage = await this.page.evaluate(() => {
57
+ const items = [];
58
+ for (let i = 0; i < window.localStorage.length; i++) {
59
+ const key = window.localStorage.key(i);
60
+ items.push({
61
+ name: key,
62
+ value: window.localStorage.getItem(key)
63
+ });
64
+ }
65
+ return items;
66
+ });
67
+
68
+ const sessionData = {
69
+ cookies: cookies,
70
+ origins: [
71
+ {
72
+ origin: 'https://www.linkedin.com',
73
+ localStorage: localStorage
74
+ }
75
+ ]
76
+ };
77
+
78
+ if (!fs.existsSync(this.linkedInSessionDir)) {
79
+ fs.mkdirSync(this.linkedInSessionDir, { recursive: true });
80
+ }
81
+
82
+ const sessionPath = this.getLinkedInSessionPath();
83
+ fs.writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
84
+
85
+ console.log('[LinkedInAutomation] LinkedIn session saved to:', sessionPath);
86
+ return true;
87
+ } catch (error) {
88
+ console.error('[LinkedInAutomation] Error saving LinkedIn session:', error);
89
+ return false;
90
+ }
91
+ }
92
+
93
+ async loadLinkedInSession() {
94
+ try {
95
+ const sessionPath = this.getLinkedInSessionPath();
96
+
97
+ if (!fs.existsSync(sessionPath)) {
98
+ console.log('[LinkedInAutomation] No saved LinkedIn session found at:', sessionPath);
99
+ return false;
100
+ }
101
+
102
+ console.log('[LinkedInAutomation] Loading LinkedIn session from:', sessionPath);
103
+
104
+ const sessionData = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
105
+
106
+ if (!this.context) {
107
+ console.error('[LinkedInAutomation] Cannot load session - no context');
108
+ return false;
109
+ }
110
+
111
+ if (sessionData.cookies && sessionData.cookies.length > 0) {
112
+ await this.context.addCookies(sessionData.cookies);
113
+ console.log(`[LinkedInAutomation] Loaded ${sessionData.cookies.length} cookies`);
114
+ }
115
+
116
+ if (sessionData.origins && sessionData.origins.length > 0) {
117
+ for (const origin of sessionData.origins) {
118
+ if (origin.localStorage && origin.localStorage.length > 0) {
119
+ console.log(`[LinkedInAutomation] Restoring ${origin.localStorage.length} localStorage items for ${origin.origin}`);
120
+ }
121
+ }
122
+ }
123
+
124
+ console.log('[LinkedInAutomation] LinkedIn session loaded successfully');
125
+ return true;
126
+ } catch (error) {
127
+ console.error('[LinkedInAutomation] Error loading LinkedIn session:', error);
128
+ return false;
129
+ }
130
+ }
131
+
132
+ async restoreLocalStorage() {
133
+ try {
134
+ const sessionPath = this.getLinkedInSessionPath();
135
+
136
+ if (!fs.existsSync(sessionPath)) {
137
+ return false;
138
+ }
139
+
140
+ const sessionData = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
141
+
142
+ if (!this.page) {
143
+ return false;
144
+ }
145
+
146
+ if (sessionData.origins && sessionData.origins.length > 0) {
147
+ for (const origin of sessionData.origins) {
148
+ if (origin.localStorage && origin.localStorage.length > 0) {
149
+ await this.page.evaluate((items) => {
150
+ for (const item of items) {
151
+ try {
152
+ window.localStorage.setItem(item.name, item.value);
153
+ } catch (e) {
154
+ console.error('Error setting localStorage item:', e);
155
+ }
156
+ }
157
+ }, origin.localStorage);
158
+ console.log(`[LinkedInAutomation] Restored ${origin.localStorage.length} localStorage items`);
159
+ }
160
+ }
161
+ }
162
+
163
+ return true;
164
+ } catch (error) {
165
+ console.error('[LinkedInAutomation] Error restoring localStorage:', error);
166
+ return false;
167
+ }
168
+ }
169
+
170
+ async startAutomation(headless = false) {
171
+ try {
172
+ console.log('[LinkedInAutomation] Starting automation...');
173
+ this.headless = headless;
174
+ this.originalHeadless = headless;
175
+ this.needsManualLogin = false;
176
+ console.log(`[LinkedInAutomation] Browser mode: ${headless ? 'headless' : 'visible'}`);
177
+ this.status = 'starting';
178
+ this.emitStatus();
179
+
180
+ this.userEmail = this._currentUserEmail || 'default_user';
181
+ console.log('[LinkedInAutomation] User email:', this.userEmail);
182
+
183
+ console.log('[LinkedInAutomation] Launching browser for login...');
184
+ await this.launchBrowserAndLogin();
185
+
186
+ this.setupPageListeners();
187
+
188
+ this.isRunning = true;
189
+ this.status = 'running';
190
+ this.emitStatus();
191
+
192
+ this.startScreenshotCapture();
193
+
194
+ } catch (error) {
195
+ console.error('[LinkedInAutomation] Failed to start automation:', error);
196
+ this.status = 'error';
197
+ this.emitStatus();
198
+ throw error;
199
+ }
200
+ }
201
+
202
+ setupPageListeners() {
203
+ if (!this.page) return;
204
+
205
+ this.page.on('load', () => {
206
+ this.updatePageInfo();
207
+ });
208
+
209
+ this.page.on('framenavigated', (frame) => {
210
+ if (frame === this.page.mainFrame()) {
211
+ this.updatePageInfo();
212
+ }
213
+ });
214
+
215
+ this.page.on('pageerror', (error) => {
216
+ console.error('[Browser Error]', error.message);
217
+ });
218
+ }
219
+
220
+ async updatePageInfo() {
221
+ if (!this.page) return;
222
+
223
+ try {
224
+ this.currentUrl = this.page.url();
225
+ this.title = await this.page.title();
226
+
227
+ console.log('[LinkedInAutomation] Page updated:', {
228
+ url: this.currentUrl,
229
+ title: this.title
230
+ });
231
+
232
+ this.emitPageUpdate();
233
+
234
+ } catch (error) {
235
+ console.error('[LinkedInAutomation] Error updating page info:', error);
236
+ }
237
+ }
238
+
239
+ async startLinkedInLogin() {
240
+ try {
241
+ if (this.originalHeadless && !this.needsManualLogin) {
242
+ console.log('[LinkedInAutomation] Manual login needed in headless mode. Switching to visible browser...');
243
+ this.needsManualLogin = true;
244
+ this.status = 'manual_login_needed';
245
+ this.emitStatus();
246
+
247
+ await this.closeBrowser();
248
+
249
+ this.headless = false;
250
+ await this.launchBrowserAndLogin();
251
+
252
+ this.setupPageListeners();
253
+ return;
254
+ }
255
+
256
+ console.log('[LinkedInAutomation] Navigating to LinkedIn login page...');
257
+ await this.page.goto('https://www.linkedin.com/login', {
258
+ waitUntil: 'domcontentloaded',
259
+ timeout: 60000
260
+ });
261
+
262
+ try {
263
+ console.log('[LinkedInAutomation] Loading credentials...');
264
+ const credentials = await readLinkedInCredentials();
265
+
266
+
267
+ await new Promise(resolve => setTimeout(resolve, 10000));
268
+
269
+ console.log('[LinkedInAutomation] Waiting for login form...');
270
+ await this.page.waitForSelector('#username', { timeout: 10000 });
271
+
272
+ console.log('[LinkedInAutomation] Filling in email with human-like typing...');
273
+ const emailInput = this.page.locator('#username');
274
+ await humanLikeType(emailInput, credentials.email);
275
+
276
+ console.log('[LinkedInAutomation] Filling in password with human-like typing...');
277
+ const passwordInput = this.page.locator('#password');
278
+ await humanLikeType(passwordInput, credentials.password);
279
+
280
+ console.log('[LinkedInAutomation] Clicking sign in button...');
281
+ await this.page.click('button[type="submit"]');
282
+
283
+ this.status = 'waiting_for_captcha';
284
+ this.emitStatus();
285
+ console.log('[LinkedInAutomation] Waiting for user to complete captcha/verification...');
286
+ } catch (credError) {
287
+ console.log('[LinkedInAutomation] No credentials found or error loading credentials:', credError.message);
288
+ console.log('[LinkedInAutomation] Waiting for user to login manually...');
289
+ this.status = 'waiting_for_manual_login';
290
+ this.emitStatus();
291
+ }
292
+
293
+ const loginStartTime = Date.now();
294
+ const loginTimeoutMs = 10 * 60 * 1000;
295
+
296
+ const pollInterval = setInterval(async () => {
297
+ try {
298
+ if (!this.page || !this.isRunning) {
299
+ console.log('[LinkedInAutomation] Login polling stopped - page:', !!this.page, 'isRunning:', this.isRunning);
300
+ clearInterval(pollInterval);
301
+ return;
302
+ }
303
+
304
+ if (Date.now() - loginStartTime > loginTimeoutMs) {
305
+ console.log('[LinkedInAutomation] LinkedIn login timed out after 10 minutes.');
306
+ this.status = 'error';
307
+ this.emitStatus();
308
+ await this.stopAutomation();
309
+ clearInterval(pollInterval);
310
+ return;
311
+ }
312
+
313
+ await this.updatePageInfo();
314
+ console.log('[LinkedInAutomation] Login polling - checking URL:', this.currentUrl);
315
+
316
+ if (this.currentUrl.includes('/feed')) {
317
+ console.log('[LinkedInAutomation] Login successful! Detected /feed/ URL');
318
+
319
+ await new Promise(resolve => setTimeout(resolve, 2000));
320
+
321
+ const secondCheck = this.page.url();
322
+ if (secondCheck.includes('/feed')) {
323
+ console.log('[LinkedInAutomation] Second check confirmed - still on /feed/');
324
+
325
+ console.log('[LinkedInAutomation] Saving LinkedIn session for future use...');
326
+ await this.saveLinkedInSession();
327
+
328
+ this.status = 'linkedin_logged_in';
329
+ this.emitStatus();
330
+
331
+ if (this.originalHeadless && this.needsManualLogin) {
332
+ console.log('[LinkedInAutomation] Manual login completed. Switching back to headless mode...');
333
+ clearInterval(pollInterval);
334
+
335
+ this.status = 'switching_to_headless';
336
+ this.emitStatus();
337
+
338
+ this.stopScreenshotCapture();
339
+
340
+ await this.closeBrowser();
341
+
342
+ this.headless = true;
343
+ console.log('[LinkedInAutomation] Relaunching browser in headless mode...');
344
+ await this.launchBrowserAndLogin();
345
+
346
+ this.setupPageListeners();
347
+ this.startScreenshotCapture();
348
+
349
+ this.status = 'linkedin_logged_in';
350
+ this.emitStatus();
351
+ }
352
+
353
+ console.log('[LinkedInAutomation] Starting action polling after successful login...');
354
+ this.startActionPolling();
355
+
356
+ clearInterval(pollInterval);
357
+ } else {
358
+ console.log('[LinkedInAutomation] Second check failed - not on /feed/ anymore, continuing to wait...');
359
+ }
360
+ }
361
+ } catch (error) {
362
+ console.error('[LinkedInAutomation] Error during login polling:', error);
363
+ }
364
+ }, 2000);
365
+
366
+ } catch (error) {
367
+ console.error('[LinkedInAutomation] Failed to navigate to LinkedIn:', error);
368
+ this.status = 'error';
369
+ this.emitStatus();
370
+ throw error;
371
+ }
372
+ }
373
+
374
+
375
+ async closeBrowser() {
376
+ try {
377
+ console.log('[LinkedInAutomation] Closing browser...');
378
+
379
+ if (this.page) {
380
+ await this.page.close();
381
+ this.page = null;
382
+ }
383
+
384
+ if (this.context) {
385
+ await this.context.close();
386
+ this.context = null;
387
+ }
388
+
389
+ if (this.browser) {
390
+ await this.browser.close();
391
+ this.browser = null;
392
+ }
393
+
394
+ console.log('[LinkedInAutomation] Browser closed successfully');
395
+ } catch (error) {
396
+ console.error('[LinkedInAutomation] Error closing browser:', error);
397
+ }
398
+ }
399
+
400
+ async stopAutomation() {
401
+ try {
402
+ console.log('[LinkedInAutomation] Stopping automation...');
403
+ this.status = 'stopping';
404
+ this.emitStatus();
405
+
406
+ this.stopScreenshotCapture();
407
+ this.stopActionPolling();
408
+
409
+ await this.closeBrowser();
410
+
411
+ this.status = 'stopped';
412
+ this.emitStatus();
413
+
414
+ } catch (error) {
415
+ console.error('[LinkedInAutomation] Error stopping automation:', error);
416
+ this.status = 'error';
417
+ this.emitStatus();
418
+ }
419
+ }
420
+
421
+ emitStatus() {
422
+ if (this.io) {
423
+ this.io.emit('automation_status', {
424
+ status: this.status,
425
+ isRunning: this.isRunning,
426
+ timestamp: new Date().toISOString()
427
+ });
428
+ }
429
+ }
430
+
431
+ emitPageUpdate() {
432
+ if (this.io) {
433
+ this.io.emit('page_update', {
434
+ url: this.currentUrl,
435
+ title: this.title,
436
+ timestamp: new Date().toISOString()
437
+ });
438
+ }
439
+ }
440
+
441
+ emitActionUpdate() {
442
+ if (this.io) {
443
+ this.io.emit('action_update', {
444
+ currentAction: this.currentAction,
445
+ timestamp: new Date().toISOString()
446
+ });
447
+ }
448
+ }
449
+
450
+ startScreenshotCapture() {
451
+ console.log('[LinkedInAutomation] Starting screenshot capture...');
452
+
453
+ this.screenshotInterval = setInterval(async () => {
454
+ await this.captureScreenshot();
455
+ }, this.screenshotIntervalMs);
456
+ }
457
+
458
+ stopScreenshotCapture() {
459
+ if (this.screenshotInterval) {
460
+ clearInterval(this.screenshotInterval);
461
+ this.screenshotInterval = null;
462
+ console.log('[LinkedInAutomation] Screenshot capture stopped');
463
+ }
464
+ }
465
+
466
+ async captureScreenshot() {
467
+ if (!this.page) return;
468
+
469
+ try {
470
+ const screenshot = await this.page.screenshot({
471
+ fullPage: false,
472
+ clip: { x: 0, y: 0, width: 1200, height: 800 },
473
+ type: 'png',
474
+ timeout: 60000
475
+ });
476
+
477
+ const base64Screenshot = screenshot.toString('base64');
478
+
479
+ this.io.emit('screenshot_update', {
480
+ screenshot: base64Screenshot,
481
+ url: this.currentUrl,
482
+ title: this.title,
483
+ timestamp: new Date().toISOString()
484
+ });
485
+
486
+ } catch (error) {
487
+ console.error('[LinkedInAutomation] Failed to capture screenshot:', error.message);
488
+ }
489
+ }
490
+
491
+ startActionPolling() {
492
+ if (this.isActionPollingActive) {
493
+ console.log('[LinkedInAutomation] Action polling is already active, skipping...');
494
+ return;
495
+ }
496
+
497
+ console.log('[LinkedInAutomation] Starting action polling with random intervals (20-40 seconds)...');
498
+ this.isActionPollingActive = true;
499
+
500
+ const pollWithRandomInterval = async () => {
501
+ if (!this.isActionPollingActive) {
502
+ console.log('[LinkedInAutomation] Action polling stopped, not scheduling next poll');
503
+ return;
504
+ }
505
+
506
+ if (this.currentAction) {
507
+ console.log('[LinkedInAutomation] Action in progress, skipping polling...');
508
+ const randomInterval = Math.floor(Math.random() * 20000) + 20000;
509
+ setTimeout(pollWithRandomInterval, randomInterval);
510
+ return;
511
+ }
512
+
513
+ await this.pollForActions();
514
+
515
+ const randomInterval = Math.floor(Math.random() * 20000) + 20000;
516
+ console.log('[LinkedInAutomation] Next poll in', randomInterval / 1000, 'seconds');
517
+
518
+ setTimeout(pollWithRandomInterval, randomInterval);
519
+ };
520
+
521
+ pollWithRandomInterval();
522
+ }
523
+
524
+ stopActionPolling() {
525
+ console.log('[LinkedInAutomation] Action polling stopped');
526
+ this.isActionPollingActive = false;
527
+ }
528
+
529
+ async pollForActions() {
530
+ try {
531
+ console.log('[LinkedInAutomation] Polling for actions from backend...');
532
+
533
+ const idToken = this.getAccessToken();
534
+ if (!idToken) {
535
+ console.error('[LinkedInAutomation] No id_token available for authentication');
536
+ return;
537
+ }
538
+
539
+ console.log('[LinkedInAutomation] Using id_token for authentication:', idToken.substring(0, 20) + '...');
540
+
541
+ const response = await fetch('https://api.coffeeinabit.com/app/actions', {
542
+ headers: {
543
+ 'Authorization': `Bearer ${idToken}`,
544
+ 'Content-Type': 'application/json'
545
+ }
546
+ });
547
+
548
+ if (response.ok) {
549
+ const responseData = await response.json();
550
+ console.log('[LinkedInAutomation] Full response from backend:', JSON.stringify(responseData, null, 2));
551
+
552
+ const actions = responseData.actions || [];
553
+ console.log('[LinkedInAutomation] Received actions from backend:', actions.length, 'actions');
554
+
555
+ if (actions && actions.length > 0) {
556
+ console.log('[LinkedInAutomation] Processing actions:', actions.map(a => `${a.action} (${a.action_id})`).join(', '));
557
+ for (const action of actions) {
558
+ await this.executeAction(action);
559
+ }
560
+ } else {
561
+ console.log('[LinkedInAutomation] No actions to execute, continuing to poll...');
562
+ }
563
+ } else {
564
+ console.error('[LinkedInAutomation] Failed to fetch actions. Status:', response.status, response.statusText);
565
+ const errorText = await response.text();
566
+ console.error('[LinkedInAutomation] Error response:', errorText);
567
+ }
568
+ } catch (error) {
569
+ console.error('[LinkedInAutomation] Error polling for actions:', error.message);
570
+ console.error('[LinkedInAutomation] Full error:', error);
571
+ }
572
+ }
573
+
574
+ async executeAction(action) {
575
+ console.log('[LinkedInAutomation] Starting execution of action:', action.action, 'ID:', action.action_id);
576
+ console.log('[LinkedInAutomation] Action parameters:', JSON.stringify(action.parameters, null, 2));
577
+ this.currentAction = action;
578
+ this.emitActionUpdate();
579
+
580
+ try {
581
+ let result = null;
582
+
583
+ switch (action.action) {
584
+ case 'get_profile':
585
+ console.log('[LinkedInAutomation] Executing get_profile action...');
586
+ result = await executeGetProfile(this.page, action);
587
+ console.log('[LinkedInAutomation] get_profile result:', result);
588
+ break;
589
+ case 'like_post':
590
+ console.log('[LinkedInAutomation] Executing like_post action...');
591
+ result = await executeLikePost(this.page, action);
592
+ console.log('[LinkedInAutomation] like_post result:', result);
593
+ break;
594
+ case 'send_connection_request':
595
+ console.log('[LinkedInAutomation] Executing send_connection_request action...');
596
+ result = await executeSendConnectionRequest(this.page, action);
597
+ console.log('[LinkedInAutomation] send_connection_request result:', result);
598
+ break;
599
+ case 'send_messages':
600
+ console.log('[LinkedInAutomation] Executing send_messages action...');
601
+ result = await executeSendMessages(this.page, action);
602
+ console.log('[LinkedInAutomation] send_messages result:', result);
603
+ break;
604
+ case 'comment_post':
605
+ console.log('[LinkedInAutomation] Executing comment_post action...');
606
+ result = await executeCommentPost(this.page, action);
607
+ console.log('[LinkedInAutomation] comment_post result:', result);
608
+ break;
609
+ case 'get_messages':
610
+ console.log('[LinkedInAutomation] Executing get_messages action...');
611
+ result = await executeGetMessages(this.page, action);
612
+ console.log('[LinkedInAutomation] get_messages result:', result);
613
+ break;
614
+ case 'check_connection_status':
615
+ console.log('[LinkedInAutomation] Executing check_connection_status action...');
616
+ result = await executeCheckConnectionStatus(this.page, action);
617
+ console.log('[LinkedInAutomation] check_connection_status result:', result);
618
+ break;
619
+ case 'get_linkedin_search_results':
620
+ console.log('[LinkedInAutomation] Executing get_linkedin_search_results action...');
621
+ result = await executeLinkedInSearch(this.page, action);
622
+ console.log('[LinkedInAutomation] get_linkedin_search_results result:', result);
623
+ break;
624
+ case 'get_daily_linkedin_connections':
625
+ console.log('[LinkedInAutomation] Executing get_daily_linkedin_connections action...');
626
+ result = await executeGetDailyLinkedInConnections(this.page, action);
627
+ console.log('[LinkedInAutomation] get_daily_linkedin_connections result:', result);
628
+ break;
629
+ case 'get_new_messages':
630
+ console.log('[LinkedInAutomation] Executing get_new_messages action...');
631
+ result = await executeGetNewMessages(this.page, action, this.getAccessToken());
632
+ console.log('[LinkedInAutomation] get_new_messages result:', result);
633
+ break;
634
+ case 'get_linkedin_updates':
635
+ console.log('[LinkedInAutomation] Executing get_linkedin_updates action...');
636
+ result = await executeGetLinkedInUpdates(this.page, action, this.getAccessToken());
637
+ console.log('[LinkedInAutomation] get_linkedin_updates result:', result);
638
+ break;
639
+ default:
640
+ console.log('[LinkedInAutomation] Unknown action type:', action.action);
641
+ result = { error: `Unknown action type: ${action.action}` };
642
+ }
643
+
644
+ console.log('[LinkedInAutomation] Reporting action result for ID:', action.action_id);
645
+ await this.reportActionResult(action.action_id, result);
646
+ console.log('[LinkedInAutomation] Action completed successfully:', action.action);
647
+
648
+ } catch (error) {
649
+ console.error('[LinkedInAutomation] Error executing action:', action.action, 'Error:', error.message);
650
+ await this.reportActionResult(action.action_id, { error: error.message });
651
+ }
652
+
653
+ this.currentAction = null;
654
+ this.emitActionUpdate();
655
+ }
656
+
657
+
658
+ async reportActionResult(actionId, result) {
659
+ try {
660
+ console.log('[LinkedInAutomation] Sending result to backend for action ID:', actionId);
661
+ console.log('[LinkedInAutomation] Result data:', JSON.stringify(result, null, 2));
662
+
663
+ const idToken = this.getAccessToken();
664
+ if (!idToken) {
665
+ console.error('[LinkedInAutomation] No id_token available for reporting result');
666
+ return;
667
+ }
668
+
669
+ const response = await fetch('https://api.coffeeinabit.com/app/actions', {
670
+ method: 'POST',
671
+ headers: {
672
+ 'Authorization': `Bearer ${idToken}`,
673
+ 'Content-Type': 'application/json'
674
+ },
675
+ body: JSON.stringify({
676
+ action_id: actionId,
677
+ result: result
678
+ })
679
+ });
680
+
681
+ if (response.ok) {
682
+ const responseData = await response.json();
683
+ console.log('[LinkedInAutomation] Successfully reported action result. Response:', responseData);
684
+ } else {
685
+ console.error('[LinkedInAutomation] Failed to report action result. Status:', response.status);
686
+ const errorText = await response.text();
687
+ console.error('[LinkedInAutomation] Error response:', errorText);
688
+ }
689
+ } catch (error) {
690
+ console.error('[LinkedInAutomation] Error reporting action result:', error);
691
+ }
692
+ }
693
+
694
+ getAccessToken() {
695
+ return this._currentAccessToken || null;
696
+ }
697
+
698
+ async launchBrowserAndLogin() {
699
+ console.log('[LinkedInAutomation] Launching Firefox for LinkedIn login...');
700
+
701
+ this.browser = await firefox.launch({ headless: this.headless || false });
702
+
703
+ this.context = await this.browser.newContext({
704
+ viewport: null,
705
+ ignoreHTTPSErrors: true
706
+ });
707
+
708
+ await this.enablePerformanceMode();
709
+ this.page = await this.context.newPage();
710
+
711
+ const sessionLoaded = await this.loadLinkedInSession();
712
+
713
+ if (sessionLoaded) {
714
+ console.log('[LinkedInAutomation] Saved session loaded, checking if still authenticated...');
715
+ await this.checkAndVerifySession();
716
+ } else {
717
+ console.log('[LinkedInAutomation] No saved session, starting fresh login...');
718
+ await this.startLinkedInLogin();
719
+ }
720
+ }
721
+
722
+ async checkAndVerifySession() {
723
+ try {
724
+ console.log('[LinkedInAutomation] Navigating to LinkedIn feed to verify session...');
725
+ await this.page.goto('https://www.linkedin.com/feed/', {
726
+ waitUntil: 'domcontentloaded',
727
+ timeout: 30000
728
+ });
729
+
730
+ await this.restoreLocalStorage();
731
+
732
+ await new Promise(resolve => setTimeout(resolve, 3000));
733
+
734
+ let verifyAttempts = 0;
735
+ const maxVerifyAttempts = 3;
736
+
737
+ while (verifyAttempts < maxVerifyAttempts) {
738
+ await this.updatePageInfo();
739
+ console.log(`[LinkedInAutomation] Verification attempt ${verifyAttempts + 1}/${maxVerifyAttempts}, URL: ${this.currentUrl}`);
740
+
741
+ if (this.currentUrl.includes('/feed')) {
742
+ console.log('[LinkedInAutomation] Session verified! Already logged in.');
743
+ this.status = 'linkedin_logged_in';
744
+ this.emitStatus();
745
+
746
+ console.log('[LinkedInAutomation] Starting action polling...');
747
+ this.startActionPolling();
748
+ return true;
749
+ }
750
+
751
+ await new Promise(resolve => setTimeout(resolve, 2000));
752
+ verifyAttempts++;
753
+ }
754
+
755
+ console.log('[LinkedInAutomation] Session verification failed - redirected to login. Starting fresh login...');
756
+ await this.startLinkedInLogin();
757
+ return false;
758
+ } catch (error) {
759
+ console.error('[LinkedInAutomation] Error verifying session:', error);
760
+ console.log('[LinkedInAutomation] Falling back to fresh login...');
761
+ await this.startLinkedInLogin();
762
+ return false;
763
+ }
764
+ }
765
+
766
+ async enablePerformanceMode() {
767
+ if (!this.context) return;
768
+
769
+ console.log('[LinkedInAutomation] Setting increased timeouts for better reliability...');
770
+ this.context.setDefaultNavigationTimeout(90000);
771
+ this.context.setDefaultTimeout(60000);
772
+ }
773
+
774
+
775
+ getStatus() {
776
+ return {
777
+ status: this.status,
778
+ isRunning: this.isRunning,
779
+ currentUrl: this.currentUrl,
780
+ title: this.title,
781
+ timestamp: new Date().toISOString()
782
+ };
783
+ }
784
+ }