coffeeinabit 0.0.46 → 0.0.48

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,172 @@
1
+ import axios from 'axios';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import { readFileSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ /**
14
+ * Version Check Helper
15
+ *
16
+ * Checks npm registry for newer version and automatically installs it if available.
17
+ */
18
+
19
+ /**
20
+ * Get current version from package.json
21
+ * @returns {string} Current version
22
+ */
23
+ function getCurrentVersion() {
24
+ try {
25
+ const packagePath = join(__dirname, '..', 'package.json');
26
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
27
+ return packageJson.version;
28
+ } catch (error) {
29
+ console.error('[VersionCheck] Error reading package.json:', error.message);
30
+ return null;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Query npm registry for latest version
36
+ * @param {string} packageName - Package name to check
37
+ * @returns {Promise<string|null>} Latest version or null if error
38
+ */
39
+ async function getLatestVersion(packageName) {
40
+ try {
41
+ const registryUrl = `https://registry.npmjs.org/${packageName}/latest`;
42
+ const response = await axios.get(registryUrl, {
43
+ timeout: 5000,
44
+ headers: {
45
+ 'Accept': 'application/json'
46
+ }
47
+ });
48
+ return response.data.version;
49
+ } catch (error) {
50
+ console.error('[VersionCheck] Error querying npm registry:', error.message);
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Compare two semantic versions
57
+ * @param {string} current - Current version
58
+ * @param {string} latest - Latest version
59
+ * @returns {boolean} True if latest is newer than current
60
+ */
61
+ function isNewerVersion(current, latest) {
62
+ if (!current || !latest) return false;
63
+
64
+ const currentParts = current.split('.').map(Number);
65
+ const latestParts = latest.split('.').map(Number);
66
+
67
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
68
+ const currentPart = currentParts[i] || 0;
69
+ const latestPart = latestParts[i] || 0;
70
+
71
+ if (latestPart > currentPart) return true;
72
+ if (latestPart < currentPart) return false;
73
+ }
74
+
75
+ return false;
76
+ }
77
+
78
+ /**
79
+ * Install the latest version globally
80
+ * @param {string} packageName - Package name to install
81
+ * @returns {Promise<boolean>} True if installation succeeded
82
+ */
83
+ async function installLatestVersion(packageName) {
84
+ try {
85
+ console.log(`[VersionCheck] Installing ${packageName}@latest...`);
86
+ const { stdout, stderr } = await execAsync(`npm install -g ${packageName}@latest`, {
87
+ timeout: 120000 // 2 minutes timeout
88
+ });
89
+
90
+ if (stderr && !stderr.includes('npm WARN')) {
91
+ console.error('[VersionCheck] Installation warnings:', stderr);
92
+ }
93
+
94
+ console.log('[VersionCheck] ✅ Successfully installed latest version');
95
+ console.log('[VersionCheck] Please restart the application to use the new version');
96
+ return true;
97
+ } catch (error) {
98
+ console.error('[VersionCheck] ❌ Failed to install latest version:', error.message);
99
+ if (error.stdout) console.log('[VersionCheck] stdout:', error.stdout);
100
+ if (error.stderr) console.error('[VersionCheck] stderr:', error.stderr);
101
+ return false;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Check for updates and install if newer version is available
107
+ * @param {string} packageName - Package name to check (default: 'coffeeinabit')
108
+ * @param {boolean} autoInstall - Whether to automatically install (default: true)
109
+ * @returns {Promise<object>} Update check result
110
+ */
111
+ export async function checkAndUpdateVersion(packageName = 'coffeeinabit', autoInstall = true) {
112
+ try {
113
+ console.log('[VersionCheck] Checking for updates...');
114
+
115
+ const currentVersion = getCurrentVersion();
116
+ if (!currentVersion) {
117
+ return {
118
+ success: false,
119
+ error: 'Could not read current version'
120
+ };
121
+ }
122
+
123
+ console.log(`[VersionCheck] Current version: ${currentVersion}`);
124
+
125
+ const latestVersion = await getLatestVersion(packageName);
126
+ if (!latestVersion) {
127
+ return {
128
+ success: false,
129
+ error: 'Could not query npm registry'
130
+ };
131
+ }
132
+
133
+ console.log(`[VersionCheck] Latest version: ${latestVersion}`);
134
+
135
+ if (!isNewerVersion(currentVersion, latestVersion)) {
136
+ console.log('[VersionCheck] ✅ You are running the latest version');
137
+ return {
138
+ success: true,
139
+ currentVersion,
140
+ latestVersion,
141
+ updateAvailable: false
142
+ };
143
+ }
144
+
145
+ console.log(`[VersionCheck] ⚠️ New version available: ${latestVersion} (current: ${currentVersion})`);
146
+
147
+ if (autoInstall) {
148
+ const installed = await installLatestVersion(packageName);
149
+ return {
150
+ success: installed,
151
+ currentVersion,
152
+ latestVersion,
153
+ updateAvailable: true,
154
+ installed
155
+ };
156
+ }
157
+
158
+ return {
159
+ success: true,
160
+ currentVersion,
161
+ latestVersion,
162
+ updateAvailable: true,
163
+ installed: false
164
+ };
165
+ } catch (error) {
166
+ console.error('[VersionCheck] Error during version check:', error.message);
167
+ return {
168
+ success: false,
169
+ error: error.message
170
+ };
171
+ }
172
+ }
@@ -12,7 +12,6 @@ import { executeSendMessages } from './tools/send_messages.js';
12
12
  import { executeCommentPost } from './tools/comment_post.js';
13
13
  import { executeGetMessages, closeAllConversationBubbles } from './tools/get_messages.js';
14
14
  import { executeLinkedInSearch } from './tools/get_linkedin_search_results.js';
15
- import { executeGetDailyLinkedInConnections } from './tools/get_daily_linkedin_connections.js';
16
15
  import { executeGetNewMessages } from './tools/get_new_messages.js';
17
16
  import { executeGetLinkedInUpdates } from './tools/get_linkedin_updates.js';
18
17
  import { safeGoto } from './tools/navigation.js';
@@ -42,6 +41,16 @@ export class LinkedInAutomation {
42
41
  this.ambientMouseTimeout = null;
43
42
  this.cloudAuth = new CloudAuth();
44
43
  this._currentRefreshToken = null;
44
+ this._currentAccessToken = null; // Track access token explicitly
45
+
46
+ // Authentication failure tracking
47
+ this.consecutiveAuthFailures = 0;
48
+ this.maxAuthFailures = 3; // Stop polling after 3 consecutive failures
49
+ this.authFailurePaused = false;
50
+ this.lastAuthFailureTime = null;
51
+
52
+ // Token re-sync callback (set externally if available)
53
+ this.tokenResyncCallback = null;
45
54
 
46
55
  // Lifecycle phases
47
56
  this.PHASE_IDLE = 'idle';
@@ -730,13 +739,43 @@ export class LinkedInAutomation {
730
739
  if (!this.ensureBrowserAvailable('pollForActions')) {
731
740
  return;
732
741
  }
742
+
743
+ // Check if polling is paused due to auth failures
744
+ if (this.authFailurePaused) {
745
+ console.log('[LinkedInAutomation] Polling paused due to authentication failures. Attempting recovery...');
746
+
747
+ // Try to re-sync tokens and recover
748
+ const idToken = await this.getAccessToken();
749
+ if (idToken && !this.cloudAuth.isTokenExpired(idToken)) {
750
+ console.log('[LinkedInAutomation] Authentication recovered, resuming polling');
751
+ this.authFailurePaused = false;
752
+ this.consecutiveAuthFailures = 0;
753
+ // Continue with normal polling below
754
+ } else {
755
+ console.log('[LinkedInAutomation] Still unable to authenticate, skipping poll');
756
+
757
+ // Emit event to notify UI
758
+ if (this.io) {
759
+ this.io.emit('automation:auth-failure', {
760
+ message: 'Authentication failed. Please re-authenticate.',
761
+ paused: true,
762
+ consecutiveFailures: this.consecutiveAuthFailures,
763
+ lastFailureTime: this.lastAuthFailureTime
764
+ });
765
+ }
766
+
767
+ return false;
768
+ }
769
+ }
770
+
733
771
  try {
734
772
  console.log('[LinkedInAutomation] Polling for actions from backend...');
735
773
 
736
774
  const idToken = await this.getAccessToken();
737
775
  if (!idToken) {
738
776
  console.error('[LinkedInAutomation] No id_token available for authentication');
739
- return;
777
+ this.handleAuthFailure('No token available');
778
+ return false;
740
779
  }
741
780
 
742
781
  console.log('[LinkedInAutomation] Using id_token for authentication:', idToken.substring(0, 20) + '...');
@@ -760,25 +799,116 @@ export class LinkedInAutomation {
760
799
  for (const action of actions) {
761
800
  await this.executeAction(action);
762
801
  }
802
+ // Reset failure counter on successful request
803
+ this.consecutiveAuthFailures = 0;
804
+ this.authFailurePaused = false;
763
805
  // Indicate there was work
764
806
  return true;
765
807
  } else {
766
808
  console.log('[LinkedInAutomation] No actions to execute, continuing to poll...');
809
+ // Reset failure counter on successful request (even with no actions)
810
+ this.consecutiveAuthFailures = 0;
811
+ this.authFailurePaused = false;
767
812
  return false;
768
813
  }
814
+ } else if (response.status === 401) {
815
+ // Handle 401 Unauthorized specifically
816
+ const errorText = await response.text();
817
+ let errorMessage;
818
+ try {
819
+ const errorJson = JSON.parse(errorText);
820
+ errorMessage = errorJson.message || 'Unauthorized';
821
+ } catch {
822
+ errorMessage = errorText || 'Unauthorized';
823
+ }
824
+
825
+ console.error('[LinkedInAutomation] Authentication failed (401 Unauthorized):', errorMessage);
826
+
827
+ // Clear current token since it's invalid
828
+ this._currentAccessToken = null;
829
+
830
+ // Try to re-sync tokens one more time
831
+ console.log('[LinkedInAutomation] Attempting token re-sync after 401 error...');
832
+ const resynced = await this.attemptTokenResync();
833
+
834
+ if (!resynced) {
835
+ this.handleAuthFailure(`401 Unauthorized: ${errorMessage}`);
836
+ } else {
837
+ // If re-sync worked, try to refresh the token
838
+ if (this.cloudAuth.isTokenExpired(this._currentAccessToken)) {
839
+ const refreshResult = await this.cloudAuth.refreshTokens(this._currentRefreshToken);
840
+ if (!refreshResult.success) {
841
+ this.handleAuthFailure(`401 Unauthorized: Token re-sync succeeded but refresh failed`);
842
+ } else {
843
+ this._currentAccessToken = refreshResult.tokens.idToken;
844
+ if (refreshResult.tokens.refreshToken) {
845
+ this._currentRefreshToken = refreshResult.tokens.refreshToken;
846
+ }
847
+ console.log('[LinkedInAutomation] Token re-sync and refresh succeeded after 401, resetting failure counter');
848
+ this.consecutiveAuthFailures = 0;
849
+ this.authFailurePaused = false;
850
+ }
851
+ } else {
852
+ console.log('[LinkedInAutomation] Token re-sync succeeded after 401, resetting failure counter');
853
+ this.consecutiveAuthFailures = 0;
854
+ this.authFailurePaused = false;
855
+ }
856
+ }
857
+
858
+ return false;
769
859
  } else {
770
860
  console.error('[LinkedInAutomation] Failed to fetch actions. Status:', response.status, response.statusText);
771
861
  const errorText = await response.text();
772
862
  console.error('[LinkedInAutomation] Error response:', errorText);
863
+ // Non-auth errors don't count as auth failures
773
864
  return false;
774
865
  }
775
866
  } catch (error) {
776
867
  console.error('[LinkedInAutomation] Error polling for actions:', error.message);
777
868
  console.error('[LinkedInAutomation] Full error:', error);
869
+
870
+ // Check if error is auth-related
871
+ if (error.message.includes('401') || error.message.includes('Unauthorized') || error.message.includes('token')) {
872
+ this.handleAuthFailure(`Network error: ${error.message}`);
873
+ }
874
+
778
875
  return false;
779
876
  }
780
877
  }
781
878
 
879
+ /**
880
+ * Handle authentication failure - track consecutive failures and pause polling if threshold reached
881
+ * @param {string} reason - Reason for the auth failure
882
+ */
883
+ handleAuthFailure(reason) {
884
+ this.consecutiveAuthFailures++;
885
+ this.lastAuthFailureTime = new Date().toISOString();
886
+
887
+ console.warn(`[LinkedInAutomation] Authentication failure #${this.consecutiveAuthFailures}: ${reason}`);
888
+
889
+ if (this.consecutiveAuthFailures >= this.maxAuthFailures) {
890
+ if (!this.authFailurePaused) {
891
+ this.authFailurePaused = true;
892
+ console.error(`[LinkedInAutomation] ⚠️ STOPPING polling after ${this.consecutiveAuthFailures} consecutive authentication failures`);
893
+ console.error('[LinkedInAutomation] Polling will resume when authentication is restored.');
894
+ console.error('[LinkedInAutomation] Please re-authenticate or check your session.');
895
+
896
+ // Emit event to notify UI
897
+ if (this.io) {
898
+ this.io.emit('automation:auth-failure', {
899
+ message: `Authentication failed after ${this.consecutiveAuthFailures} attempts. Polling paused.`,
900
+ paused: true,
901
+ consecutiveFailures: this.consecutiveAuthFailures,
902
+ lastFailureTime: this.lastAuthFailureTime,
903
+ reason: reason
904
+ });
905
+ }
906
+ }
907
+ } else {
908
+ console.warn(`[LinkedInAutomation] Will pause polling after ${this.maxAuthFailures - this.consecutiveAuthFailures} more failures`);
909
+ }
910
+ }
911
+
782
912
  async executeAction(action) {
783
913
  const actionLogs = [];
784
914
  const originalConsoleLog = console.log;
@@ -858,10 +988,6 @@ export class LinkedInAutomation {
858
988
  console.log('[LinkedInAutomation] Executing get_linkedin_search_results action...');
859
989
  result = await executeLinkedInSearch(this.page, action);
860
990
  break;
861
- case 'get_daily_linkedin_connections':
862
- console.log('[LinkedInAutomation] Executing get_daily_linkedin_connections action...');
863
- result = await executeGetDailyLinkedInConnections(this.page, action);
864
- break;
865
991
  case 'get_new_messages':
866
992
  console.log('[LinkedInAutomation] Executing get_new_messages action...');
867
993
  result = await executeGetNewMessages(this.page, action, await this.getAccessToken());
@@ -1053,11 +1179,64 @@ export class LinkedInAutomation {
1053
1179
  }
1054
1180
  }
1055
1181
 
1182
+ /**
1183
+ * Set a callback function to re-sync tokens from external source (e.g., session store)
1184
+ * @param {Function} callback - Function that returns Promise<{idToken, refreshToken}> or null
1185
+ */
1186
+ setTokenResyncCallback(callback) {
1187
+ this.tokenResyncCallback = callback;
1188
+ }
1189
+
1190
+ /**
1191
+ * Attempt to re-sync tokens from external source (session, etc.)
1192
+ * @returns {Promise<boolean>} true if tokens were successfully re-synced
1193
+ */
1194
+ async attemptTokenResync() {
1195
+ if (!this.tokenResyncCallback) {
1196
+ return false;
1197
+ }
1198
+
1199
+ try {
1200
+ const tokens = await this.tokenResyncCallback();
1201
+ if (tokens && tokens.idToken && tokens.refreshToken) {
1202
+ console.log('[LinkedInAutomation] Tokens re-synced from external source');
1203
+ this._currentAccessToken = tokens.idToken;
1204
+ this._currentRefreshToken = tokens.refreshToken;
1205
+
1206
+ // If tokens are expired, try to refresh them
1207
+ if (this.cloudAuth.isTokenExpired(this._currentAccessToken)) {
1208
+ console.log('[LinkedInAutomation] Re-synced tokens are expired, attempting refresh...');
1209
+ const result = await this.cloudAuth.refreshTokens(this._currentRefreshToken);
1210
+ if (result.success) {
1211
+ this._currentAccessToken = result.tokens.idToken;
1212
+ if (result.tokens.refreshToken) {
1213
+ this._currentRefreshToken = result.tokens.refreshToken;
1214
+ }
1215
+ } else {
1216
+ console.error('[LinkedInAutomation] Failed to refresh re-synced tokens:', result.error);
1217
+ return false;
1218
+ }
1219
+ }
1220
+
1221
+ return true;
1222
+ }
1223
+ } catch (error) {
1224
+ console.error('[LinkedInAutomation] Error during token re-sync:', error.message);
1225
+ }
1226
+
1227
+ return false;
1228
+ }
1229
+
1056
1230
  async getAccessToken() {
1057
- // Check if token exists
1231
+ // First, try to get current token
1058
1232
  if (!this._currentAccessToken) {
1059
- console.error('[LinkedInAutomation] No access token available');
1060
- return null;
1233
+ console.log('[LinkedInAutomation] No access token available, attempting re-sync...');
1234
+ const resynced = await this.attemptTokenResync();
1235
+ if (!resynced) {
1236
+ console.error('[LinkedInAutomation] No access token available and re-sync failed');
1237
+ return null;
1238
+ }
1239
+ // Token was re-synced, continue to validation below
1061
1240
  }
1062
1241
 
1063
1242
  // Check if token is expired or expiring soon (within 5 minutes)
@@ -1066,8 +1245,12 @@ export class LinkedInAutomation {
1066
1245
 
1067
1246
  // Check if refresh token is available
1068
1247
  if (!this._currentRefreshToken) {
1069
- console.error('[LinkedInAutomation] No refresh token available, cannot refresh');
1070
- return null;
1248
+ console.log('[LinkedInAutomation] No refresh token available, attempting re-sync...');
1249
+ const resynced = await this.attemptTokenResync();
1250
+ if (!resynced || !this._currentRefreshToken) {
1251
+ console.error('[LinkedInAutomation] No refresh token available and re-sync failed');
1252
+ return null;
1253
+ }
1071
1254
  }
1072
1255
 
1073
1256
  // Attempt to refresh the token
@@ -1081,17 +1264,48 @@ export class LinkedInAutomation {
1081
1264
  if (result.tokens.refreshToken) {
1082
1265
  this._currentRefreshToken = result.tokens.refreshToken;
1083
1266
  }
1267
+ // Reset failure counter on successful refresh
1268
+ this.consecutiveAuthFailures = 0;
1269
+ this.authFailurePaused = false;
1084
1270
  return this._currentAccessToken;
1085
1271
  } else {
1086
1272
  console.error('[LinkedInAutomation] Token refresh failed:', result.error);
1273
+
1274
+ // Try one more time to re-sync before giving up
1275
+ console.log('[LinkedInAutomation] Attempting token re-sync after refresh failure...');
1276
+ const resynced = await this.attemptTokenResync();
1277
+ if (resynced && !this.cloudAuth.isTokenExpired(this._currentAccessToken)) {
1278
+ console.log('[LinkedInAutomation] Token re-synced successfully after refresh failure');
1279
+ this.consecutiveAuthFailures = 0;
1280
+ this.authFailurePaused = false;
1281
+ return this._currentAccessToken;
1282
+ }
1283
+
1087
1284
  return null;
1088
1285
  }
1089
1286
  } catch (error) {
1090
1287
  console.error('[LinkedInAutomation] Error during token refresh:', error.message);
1288
+
1289
+ // Try re-sync one more time
1290
+ const resynced = await this.attemptTokenResync();
1291
+ if (resynced && !this.cloudAuth.isTokenExpired(this._currentAccessToken)) {
1292
+ console.log('[LinkedInAutomation] Token re-synced successfully after refresh error');
1293
+ this.consecutiveAuthFailures = 0;
1294
+ this.authFailurePaused = false;
1295
+ return this._currentAccessToken;
1296
+ }
1297
+
1091
1298
  return null;
1092
1299
  }
1093
1300
  }
1094
1301
 
1302
+ // Token is valid, reset failure counter
1303
+ if (this.consecutiveAuthFailures > 0) {
1304
+ console.log('[LinkedInAutomation] Authentication recovered, resetting failure counter');
1305
+ this.consecutiveAuthFailures = 0;
1306
+ this.authFailurePaused = false;
1307
+ }
1308
+
1095
1309
  return this._currentAccessToken;
1096
1310
  }
1097
1311
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coffeeinabit",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "coffeeinabit app",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "axios": "^1.6.0",
25
+ "coffeeinabit": "^0.0.47",
25
26
  "dotenv": "^16.3.1",
26
27
  "express": "^4.18.2",
27
28
  "express-session": "^1.17.3",
package/server.js CHANGED
@@ -17,6 +17,7 @@ import { createAuthRoutes } from './routes/auth.js';
17
17
  import { createAutomationRoutes, createStateRoutes } from './routes/automation.js';
18
18
  import { setupSocketHandlers } from './socket/handlers.js';
19
19
  import { tryAutoStartAutomation } from './helpers/auto_start.js';
20
+ import { checkAndUpdateVersion } from './helpers/version_check.js';
20
21
 
21
22
  dotenv.config();
22
23
 
@@ -36,9 +37,53 @@ const appState = new AppState();
36
37
  // Legacy single automation instance (for backward compatibility during migration)
37
38
  // TODO: Remove after full migration to multi-process state
38
39
  const linkedinAutomation = new LinkedInAutomation(io);
39
- linkedinAutomation.getAccessToken = function() {
40
- return this._currentAccessToken || null;
41
- };
40
+
41
+ // Set up token re-sync callback for legacy automation instance
42
+ // This allows the automation to re-sync tokens from the session store when they expire
43
+ linkedinAutomation.setTokenResyncCallback(async () => {
44
+ try {
45
+ // Try to get tokens from session store for the user
46
+ if (!linkedinAutomation._currentUserEmail) {
47
+ return null;
48
+ }
49
+
50
+ // Get all sessions and find one for this user
51
+ if (typeof sessionStore.all === 'function') {
52
+ return new Promise((resolve) => {
53
+ sessionStore.all((err, sessions) => {
54
+ if (err || !sessions) {
55
+ resolve(null);
56
+ return;
57
+ }
58
+
59
+ // Find a session for this user
60
+ for (const sessionId in sessions) {
61
+ const session = sessions[sessionId];
62
+ if (session && session.user && session.user.email === linkedinAutomation._currentUserEmail) {
63
+ // Ensure tokens are valid
64
+ cloudAuth.ensureValidToken(session).then(result => {
65
+ if (result.valid && session.tokens) {
66
+ resolve({
67
+ idToken: session.tokens.idToken,
68
+ refreshToken: session.tokens.refreshToken
69
+ });
70
+ } else {
71
+ resolve(null);
72
+ }
73
+ }).catch(() => resolve(null));
74
+ return;
75
+ }
76
+ }
77
+ resolve(null);
78
+ });
79
+ });
80
+ }
81
+ } catch (error) {
82
+ console.error('[Server] Error in token re-sync callback:', error.message);
83
+ return null;
84
+ }
85
+ return null;
86
+ });
42
87
 
43
88
  // Initialize session store
44
89
  const FileStoreSession = FileStore(session);
@@ -94,6 +139,9 @@ app.use('/auth', createAuthRoutes(cloudAuth, PORT, appState, linkedinAutomation)
94
139
  app.use('/api/automation', createAutomationRoutes(cloudAuth, appState, linkedinAutomation, io));
95
140
  app.use('/api/state', createStateRoutes(cloudAuth, appState));
96
141
 
142
+ // Set up token re-sync dependencies for AppState
143
+ appState.setTokenResyncDependencies(cloudAuth, sessionStore);
144
+
97
145
  // Setup WebSocket handlers
98
146
  setupSocketHandlers(io, appState);
99
147
 
@@ -104,6 +152,11 @@ server.listen(PORT, async () => {
104
152
  console.log(' Press Ctrl+C to stop');
105
153
  console.log('');
106
154
 
155
+ // Check for updates on startup (non-blocking)
156
+ checkAndUpdateVersion('coffeeinabit', true).catch(err => {
157
+ console.error('[Server] Version check failed:', err.message);
158
+ });
159
+
107
160
  // Wait a bit for everything to initialize, then try to auto-start automation
108
161
  setTimeout(() => {
109
162
  tryAutoStartAutomation(sessionStore, contextDir, cloudAuth, linkedinAutomation);
@@ -18,6 +18,10 @@ export class AppState {
18
18
  // Private state: Map of socketId -> process state
19
19
  this._processes = new Map();
20
20
 
21
+ // Dependencies for token re-sync (set externally)
22
+ this._cloudAuth = null;
23
+ this._sessionStore = null;
24
+
21
25
  // Statistics
22
26
  this._stats = {
23
27
  totalProcessesCreated: 0,
@@ -26,6 +30,16 @@ export class AppState {
26
30
  };
27
31
  }
28
32
 
33
+ /**
34
+ * Set dependencies for token re-sync
35
+ * @param {object} cloudAuth - CloudAuth instance
36
+ * @param {object} sessionStore - Session store instance
37
+ */
38
+ setTokenResyncDependencies(cloudAuth, sessionStore) {
39
+ this._cloudAuth = cloudAuth;
40
+ this._sessionStore = sessionStore;
41
+ }
42
+
29
43
  /**
30
44
  * Create a new automation process for a socket connection
31
45
  * @param {string} socketId - WebSocket connection ID
@@ -40,9 +54,54 @@ export class AppState {
40
54
  }
41
55
 
42
56
  const automation = new LinkedInAutomation(io);
43
- automation.getAccessToken = function() {
44
- return this._currentAccessToken || null;
45
- };
57
+
58
+ // Set up token re-sync callback if dependencies are available
59
+ if (this._cloudAuth && this._sessionStore) {
60
+ const userEmail = session?.user?.email;
61
+ automation.setTokenResyncCallback(async () => {
62
+ try {
63
+ if (!userEmail) {
64
+ return null;
65
+ }
66
+
67
+ // Try to get tokens from session store for this user
68
+ if (typeof this._sessionStore.all === 'function') {
69
+ return new Promise((resolve) => {
70
+ this._sessionStore.all((err, sessions) => {
71
+ if (err || !sessions) {
72
+ resolve(null);
73
+ return;
74
+ }
75
+
76
+ // Find a session for this user
77
+ for (const sessionId in sessions) {
78
+ const sess = sessions[sessionId];
79
+ if (sess && sess.user && sess.user.email === userEmail) {
80
+ // Ensure tokens are valid
81
+ this._cloudAuth.ensureValidToken(sess).then(result => {
82
+ if (result.valid && sess.tokens) {
83
+ resolve({
84
+ idToken: sess.tokens.idToken,
85
+ refreshToken: sess.tokens.refreshToken
86
+ });
87
+ } else {
88
+ resolve(null);
89
+ }
90
+ }).catch(() => resolve(null));
91
+ return;
92
+ }
93
+ }
94
+ resolve(null);
95
+ });
96
+ });
97
+ }
98
+ } catch (error) {
99
+ console.error('[AppState] Error in token re-sync callback:', error.message);
100
+ return null;
101
+ }
102
+ return null;
103
+ });
104
+ }
46
105
 
47
106
  const processState = {
48
107
  socketId,