coffeeinabit 0.0.35 → 0.0.46
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/Makefile +12 -3
- package/cloud_auth.js +170 -170
- package/helpers/auto_start.js +165 -0
- package/helpers/token_sync.js +77 -0
- package/linkedin_automation.js +1450 -1285
- package/middleware/auth.js +38 -0
- package/package.json +35 -35
- package/public/dashboard.html +3 -3
- package/public/login.html +126 -126
- package/public/styles.css +490 -490
- package/routes/auth.js +110 -0
- package/routes/automation.js +199 -0
- package/server.js +111 -217
- package/socket/handlers.js +48 -0
- package/state/app_state.js +214 -0
- package/tools/comment_post.js +155 -155
- package/tools/context_paths.js +24 -24
- package/tools/cookie_manager.js +149 -149
- package/tools/get_daily_linkedin_connections.js +236 -236
- package/tools/get_linkedin_search_results.js +142 -142
- package/tools/get_linkedin_updates.js +64 -51
- package/tools/get_messages.js +388 -388
- package/tools/get_new_messages.js +449 -449
- package/tools/get_profile.js +655 -459
- package/tools/human_mouse.js +190 -190
- package/tools/human_typing.js +83 -67
- package/tools/like_post.js +104 -104
- package/tools/navigation.js +32 -32
- package/tools/send_connection_request.js +253 -238
- package/tools/send_messages.js +450 -417
package/Makefile
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
patch-and-publish:
|
|
2
|
-
npm
|
|
3
|
-
npm
|
|
1
|
+
patch-and-publish:
|
|
2
|
+
@echo "Checking npm authentication..."
|
|
3
|
+
@npm whoami || (echo "Error: Not logged in to npm. Run 'npm login' first." && exit 1)
|
|
4
|
+
@echo "Authenticated as: $$(npm whoami)"
|
|
5
|
+
@echo "Verifying npm token in ~/.npmrc..."
|
|
6
|
+
@grep -q "//registry.npmjs.org/:_authToken=" ~/.npmrc || (echo "Error: No npm token found in ~/.npmrc" && exit 1)
|
|
7
|
+
@echo "Token found. Publishing..."
|
|
8
|
+
npm version patch
|
|
9
|
+
npm publish --access public || (echo "" && echo "❌ Publish failed! If you see a 2FA error:" && echo " 1. Go to: https://www.npmjs.com/settings/kate_yan/tokens" && echo " 2. Create a 'Granular Access Token'" && echo " 3. Enable 'Bypass 2FA' permission" && echo " 4. Update ~/.npmrc with: //registry.npmjs.org/:_authToken=YOUR_NEW_TOKEN" && exit 1)
|
|
10
|
+
|
|
11
|
+
stop:
|
|
12
|
+
pm2 stop "npx coffeeinabit" && pm2 delete "npx coffeeinabit" && pkill -9 -f "npm exec coffeeinabit"; pkill -9 -f "npx.*coffeeinabit"; pkill -9 -f "node.*coffeeinabit"
|
package/cloud_auth.js
CHANGED
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
|
|
3
|
-
export class CloudAuth {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.sessionSecret = process.env.SESSION_SECRET || 'coffeeinabit-secret-key-change-in-production';
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async getLoginUrl(redirectUri) {
|
|
9
|
-
try {
|
|
10
|
-
const params = new URLSearchParams({
|
|
11
|
-
client_id: '4rhqg2pjfhjkq42mr7iilpnmsu',
|
|
12
|
-
response_type: 'code',
|
|
13
|
-
redirect_uri: redirectUri,
|
|
14
|
-
scope: 'openid email profile'
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const loginUrl = `https://coffeeinabit.auth.us-east-1.amazoncognito.com/login?${params.toString()}`;
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
success: true,
|
|
21
|
-
loginUrl: loginUrl
|
|
22
|
-
};
|
|
23
|
-
} catch (error) {
|
|
24
|
-
return {
|
|
25
|
-
success: false,
|
|
26
|
-
error: error.message
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async exchangeCodeForTokens(code, redirectUri) {
|
|
32
|
-
try {
|
|
33
|
-
console.log('[CloudAuth] Received authorization code:', code.substring(0, 10) + '...');
|
|
34
|
-
console.log('[CloudAuth] Using redirect_uri:', redirectUri);
|
|
35
|
-
|
|
36
|
-
const response = await axios.get(`https://api.coffeeinabit.com/auth/token?code=${encodeURIComponent(code)}&redirect_url=${encodeURIComponent(redirectUri)}`);
|
|
37
|
-
|
|
38
|
-
if (response.data.error) {
|
|
39
|
-
throw new Error(response.data.error);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const { id_token, access_token, refresh_token } = response.data;
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
success: true,
|
|
46
|
-
tokens: {
|
|
47
|
-
idToken: id_token,
|
|
48
|
-
accessToken: access_token,
|
|
49
|
-
refreshToken: refresh_token
|
|
50
|
-
},
|
|
51
|
-
user: this.extractUserFromToken(id_token)
|
|
52
|
-
};
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('[CloudAuth] Token exchange failed:', error.response?.status, error.response?.data);
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
error: error.message
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async refreshTokens(refreshToken) {
|
|
63
|
-
try {
|
|
64
|
-
const response = await axios.get(
|
|
65
|
-
`https://api.coffeeinabit.com/auth/refresh?refresh_token=${encodeURIComponent(refreshToken)}`
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
if (response.data.error) {
|
|
69
|
-
throw new Error(response.data.error);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
success: true,
|
|
74
|
-
tokens: {
|
|
75
|
-
idToken: response.data.id_token,
|
|
76
|
-
accessToken: response.data.access_token,
|
|
77
|
-
refreshToken: response.data.refresh_token || refreshToken
|
|
78
|
-
},
|
|
79
|
-
user: this.extractUserFromToken(response.data.id_token)
|
|
80
|
-
};
|
|
81
|
-
} catch (error) {
|
|
82
|
-
return {
|
|
83
|
-
success: false,
|
|
84
|
-
error: error.message
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
extractUserFromToken(idToken) {
|
|
90
|
-
if (!idToken) return null;
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
|
|
94
|
-
return {
|
|
95
|
-
email: payload.email || payload['cognito:username'],
|
|
96
|
-
userId: payload.sub
|
|
97
|
-
};
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.error('[CloudAuth] Error extracting user from token:', error.message);
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
isTokenExpired(token) {
|
|
105
|
-
if (!token) return true;
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
|
109
|
-
if (!payload || !payload.exp) return true;
|
|
110
|
-
|
|
111
|
-
const bufferTime = 5 * 60;
|
|
112
|
-
return payload.exp < (Date.now() / 1000) + bufferTime;
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error('[CloudAuth] Error checking token expiration:', error.message);
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async ensureValidToken(session) {
|
|
120
|
-
if (!session.tokens?.idToken) {
|
|
121
|
-
return { valid: false, error: 'No token available' };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (this.isTokenExpired(session.tokens.idToken)) {
|
|
125
|
-
console.log('[CloudAuth] Token expired or expiring soon, attempting refresh...');
|
|
126
|
-
|
|
127
|
-
if (!session.tokens.refreshToken) {
|
|
128
|
-
return { valid: false, error: 'No refresh token available' };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const result = await this.refreshTokens(session.tokens.refreshToken);
|
|
132
|
-
|
|
133
|
-
if (result.success) {
|
|
134
|
-
this.storeTokensInSession(session, result.tokens, result.user);
|
|
135
|
-
return { valid: true };
|
|
136
|
-
} else {
|
|
137
|
-
return { valid: false, error: result.error };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return { valid: true };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
storeTokensInSession(session, tokens, user) {
|
|
145
|
-
session.tokens = tokens;
|
|
146
|
-
session.user = user;
|
|
147
|
-
console.log('[CloudAuth] Tokens stored in session for user:', user?.email);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
clearSession(session) {
|
|
151
|
-
session.tokens = null;
|
|
152
|
-
session.user = null;
|
|
153
|
-
console.log('[CloudAuth] Session cleared');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
isAuthenticated(session) {
|
|
157
|
-
return !!(session.tokens && session.tokens.idToken && session.tokens.accessToken);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
getAuthStatus(session) {
|
|
161
|
-
if (this.isAuthenticated(session)) {
|
|
162
|
-
return {
|
|
163
|
-
authenticated: true,
|
|
164
|
-
user: session.user || null
|
|
165
|
-
};
|
|
166
|
-
} else {
|
|
167
|
-
return { authenticated: false };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
export class CloudAuth {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.sessionSecret = process.env.SESSION_SECRET || 'coffeeinabit-secret-key-change-in-production';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async getLoginUrl(redirectUri) {
|
|
9
|
+
try {
|
|
10
|
+
const params = new URLSearchParams({
|
|
11
|
+
client_id: '4rhqg2pjfhjkq42mr7iilpnmsu',
|
|
12
|
+
response_type: 'code',
|
|
13
|
+
redirect_uri: redirectUri,
|
|
14
|
+
scope: 'openid email profile'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const loginUrl = `https://coffeeinabit.auth.us-east-1.amazoncognito.com/login?${params.toString()}`;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
success: true,
|
|
21
|
+
loginUrl: loginUrl
|
|
22
|
+
};
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
error: error.message
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async exchangeCodeForTokens(code, redirectUri) {
|
|
32
|
+
try {
|
|
33
|
+
console.log('[CloudAuth] Received authorization code:', code.substring(0, 10) + '...');
|
|
34
|
+
console.log('[CloudAuth] Using redirect_uri:', redirectUri);
|
|
35
|
+
|
|
36
|
+
const response = await axios.get(`https://api.coffeeinabit.com/auth/token?code=${encodeURIComponent(code)}&redirect_url=${encodeURIComponent(redirectUri)}`);
|
|
37
|
+
|
|
38
|
+
if (response.data.error) {
|
|
39
|
+
throw new Error(response.data.error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { id_token, access_token, refresh_token } = response.data;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
tokens: {
|
|
47
|
+
idToken: id_token,
|
|
48
|
+
accessToken: access_token,
|
|
49
|
+
refreshToken: refresh_token
|
|
50
|
+
},
|
|
51
|
+
user: this.extractUserFromToken(id_token)
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('[CloudAuth] Token exchange failed:', error.response?.status, error.response?.data);
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: error.message
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async refreshTokens(refreshToken) {
|
|
63
|
+
try {
|
|
64
|
+
const response = await axios.get(
|
|
65
|
+
`https://api.coffeeinabit.com/auth/refresh?refresh_token=${encodeURIComponent(refreshToken)}`
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (response.data.error) {
|
|
69
|
+
throw new Error(response.data.error);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
tokens: {
|
|
75
|
+
idToken: response.data.id_token,
|
|
76
|
+
accessToken: response.data.access_token,
|
|
77
|
+
refreshToken: response.data.refresh_token || refreshToken
|
|
78
|
+
},
|
|
79
|
+
user: this.extractUserFromToken(response.data.id_token)
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
error: error.message
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
extractUserFromToken(idToken) {
|
|
90
|
+
if (!idToken) return null;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
|
|
94
|
+
return {
|
|
95
|
+
email: payload.email || payload['cognito:username'],
|
|
96
|
+
userId: payload.sub
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('[CloudAuth] Error extracting user from token:', error.message);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
isTokenExpired(token) {
|
|
105
|
+
if (!token) return true;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
|
|
109
|
+
if (!payload || !payload.exp) return true;
|
|
110
|
+
|
|
111
|
+
const bufferTime = 5 * 60;
|
|
112
|
+
return payload.exp < (Date.now() / 1000) + bufferTime;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('[CloudAuth] Error checking token expiration:', error.message);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async ensureValidToken(session) {
|
|
120
|
+
if (!session.tokens?.idToken) {
|
|
121
|
+
return { valid: false, error: 'No token available' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this.isTokenExpired(session.tokens.idToken)) {
|
|
125
|
+
console.log('[CloudAuth] Token expired or expiring soon, attempting refresh...');
|
|
126
|
+
|
|
127
|
+
if (!session.tokens.refreshToken) {
|
|
128
|
+
return { valid: false, error: 'No refresh token available' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const result = await this.refreshTokens(session.tokens.refreshToken);
|
|
132
|
+
|
|
133
|
+
if (result.success) {
|
|
134
|
+
this.storeTokensInSession(session, result.tokens, result.user);
|
|
135
|
+
return { valid: true };
|
|
136
|
+
} else {
|
|
137
|
+
return { valid: false, error: result.error };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { valid: true };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
storeTokensInSession(session, tokens, user) {
|
|
145
|
+
session.tokens = tokens;
|
|
146
|
+
session.user = user;
|
|
147
|
+
console.log('[CloudAuth] Tokens stored in session for user:', user?.email);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
clearSession(session) {
|
|
151
|
+
session.tokens = null;
|
|
152
|
+
session.user = null;
|
|
153
|
+
console.log('[CloudAuth] Session cleared');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
isAuthenticated(session) {
|
|
157
|
+
return !!(session.tokens && session.tokens.idToken && session.tokens.accessToken);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getAuthStatus(session) {
|
|
161
|
+
if (this.isAuthenticated(session)) {
|
|
162
|
+
return {
|
|
163
|
+
authenticated: true,
|
|
164
|
+
user: session.user || null
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
return { authenticated: false };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Auto-Start Helper
|
|
6
|
+
*
|
|
7
|
+
* Handles automatic startup of LinkedIn automation when the server starts
|
|
8
|
+
* if there are existing authenticated sessions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Attempt to automatically start automation if user is authenticated
|
|
13
|
+
* Note: Auto-start uses legacy single instance for backward compatibility.
|
|
14
|
+
* New implementations should wait for WebSocket connections.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} sessionStore - Express session store
|
|
17
|
+
* @param {string} contextDir - Context directory path
|
|
18
|
+
* @param {object} cloudAuth - CloudAuth instance
|
|
19
|
+
* @param {object} linkedinAutomation - LinkedInAutomation instance
|
|
20
|
+
*/
|
|
21
|
+
export async function tryAutoStartAutomation(sessionStore, contextDir, cloudAuth, linkedinAutomation) {
|
|
22
|
+
try {
|
|
23
|
+
// First, try using sessionStore.all if available
|
|
24
|
+
if (typeof sessionStore.all === 'function') {
|
|
25
|
+
sessionStore.all(async (err, sessions) => {
|
|
26
|
+
if (err) {
|
|
27
|
+
console.log('[AutoStart] Could not retrieve sessions for auto-start:', err.message);
|
|
28
|
+
// Fall through to file-based enumeration
|
|
29
|
+
await tryFileBasedAutoStart(contextDir, cloudAuth, linkedinAutomation);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!sessions || Object.keys(sessions).length === 0) {
|
|
34
|
+
console.log('[AutoStart] No existing sessions found, skipping auto-start');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Try to find an authenticated session
|
|
39
|
+
for (const sessionId in sessions) {
|
|
40
|
+
const sessionData = sessions[sessionId];
|
|
41
|
+
if (sessionData && sessionData.tokens && sessionData.tokens.idToken && sessionData.tokens.accessToken) {
|
|
42
|
+
const success = await tryStartAutomationWithSession(sessionData, cloudAuth, linkedinAutomation);
|
|
43
|
+
if (success) return; // Successfully started, exit function
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('[AutoStart] No valid authenticated session found, skipping auto-start');
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
// Fall back to file-based enumeration
|
|
51
|
+
await tryFileBasedAutoStart(contextDir, cloudAuth, linkedinAutomation);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('[AutoStart] Error during auto-start check:', error.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Try to start automation by reading session files directly from the filesystem
|
|
60
|
+
*/
|
|
61
|
+
async function tryFileBasedAutoStart(contextDir, cloudAuth, linkedinAutomation) {
|
|
62
|
+
try {
|
|
63
|
+
// session-file-store may store sessions in a 'sessions' subdirectory or directly in contextDir
|
|
64
|
+
let sessionsDir = path.join(contextDir, 'sessions');
|
|
65
|
+
let sessionFiles = [];
|
|
66
|
+
|
|
67
|
+
// First try the sessions subdirectory
|
|
68
|
+
if (fs.existsSync(sessionsDir)) {
|
|
69
|
+
sessionFiles = fs.readdirSync(sessionsDir)
|
|
70
|
+
.filter(file => file.endsWith('.json'))
|
|
71
|
+
.map(file => path.join(sessionsDir, file));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If no files found in subdirectory, check the context directory directly
|
|
75
|
+
if (sessionFiles.length === 0 && fs.existsSync(contextDir)) {
|
|
76
|
+
const allFiles = fs.readdirSync(contextDir).filter(file => file.endsWith('.json'));
|
|
77
|
+
// Filter out non-session files (like storage_state files)
|
|
78
|
+
sessionFiles = allFiles
|
|
79
|
+
.filter(file => {
|
|
80
|
+
const filePath = path.join(contextDir, file);
|
|
81
|
+
try {
|
|
82
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
83
|
+
const data = JSON.parse(content);
|
|
84
|
+
// Session files have tokens and cookie structure
|
|
85
|
+
return data.tokens && data.cookie;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.map(file => path.join(contextDir, file));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (sessionFiles.length === 0) {
|
|
94
|
+
console.log('[AutoStart] No session files found, skipping auto-start');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(`[AutoStart] Found ${sessionFiles.length} session file(s), checking for authenticated sessions...`);
|
|
99
|
+
|
|
100
|
+
// Try each session file
|
|
101
|
+
for (const sessionPath of sessionFiles) {
|
|
102
|
+
try {
|
|
103
|
+
const fileContent = fs.readFileSync(sessionPath, 'utf8');
|
|
104
|
+
const sessionData = JSON.parse(fileContent);
|
|
105
|
+
|
|
106
|
+
if (sessionData && sessionData.tokens && sessionData.tokens.idToken && sessionData.tokens.accessToken) {
|
|
107
|
+
const success = await tryStartAutomationWithSession(sessionData, cloudAuth, linkedinAutomation);
|
|
108
|
+
if (success) return; // Successfully started, exit function
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// Skip invalid session files
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('[AutoStart] No valid authenticated session found in session files, skipping auto-start');
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log('[AutoStart] Error reading session files:', error.message);
|
|
119
|
+
console.log('[AutoStart] Automation will start when clients connect via WebSocket');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Try to start automation with a session object
|
|
125
|
+
* @param {object} sessionData - Session data object with tokens and user info
|
|
126
|
+
* @param {object} cloudAuth - CloudAuth instance
|
|
127
|
+
* @param {object} linkedinAutomation - LinkedInAutomation instance
|
|
128
|
+
* @returns {Promise<boolean>} true if automation started successfully
|
|
129
|
+
*/
|
|
130
|
+
export async function tryStartAutomationWithSession(sessionData, cloudAuth, linkedinAutomation) {
|
|
131
|
+
try {
|
|
132
|
+
// Check if automation is already running
|
|
133
|
+
if (linkedinAutomation.isRunning) {
|
|
134
|
+
console.log('[AutoStart] Automation is already running, skipping auto-start');
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create a session-like object that works with cloudAuth methods
|
|
139
|
+
const sessionObj = {
|
|
140
|
+
tokens: sessionData.tokens,
|
|
141
|
+
user: sessionData.user
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Validate and refresh token if needed
|
|
145
|
+
const tokenCheck = await cloudAuth.ensureValidToken(sessionObj);
|
|
146
|
+
if (!tokenCheck.valid) {
|
|
147
|
+
console.log('[AutoStart] Session token invalid, skipping auto-start');
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Start automation with the authenticated session (legacy mode)
|
|
152
|
+
// Note: This uses the single instance for backward compatibility
|
|
153
|
+
// New implementations should wait for WebSocket connections
|
|
154
|
+
console.log('[AutoStart] Auto-starting automation with authenticated session (legacy mode)...');
|
|
155
|
+
linkedinAutomation._currentAccessToken = sessionObj.tokens.idToken;
|
|
156
|
+
linkedinAutomation._currentRefreshToken = sessionObj.tokens.refreshToken;
|
|
157
|
+
linkedinAutomation._currentUserEmail = sessionObj.user?.email || 'default_user';
|
|
158
|
+
await linkedinAutomation.startAutomation(true); // Start in headless mode by default
|
|
159
|
+
console.log('[AutoStart] ✅ Automation auto-started successfully (legacy mode)');
|
|
160
|
+
return true;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('[AutoStart] Error during auto-start:', error.message);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Synchronization Helper
|
|
3
|
+
*
|
|
4
|
+
* Handles synchronization of authentication tokens between sessions
|
|
5
|
+
* and LinkedIn automation processes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sync tokens from session to LinkedInAutomation
|
|
10
|
+
* Supports both legacy single instance and new multi-process state
|
|
11
|
+
*
|
|
12
|
+
* @param {object} session - Express session object
|
|
13
|
+
* @param {string|null} socketId - Optional socket ID for process-specific sync
|
|
14
|
+
* @param {object} appState - AppState instance (optional, for multi-process)
|
|
15
|
+
* @param {object} cloudAuth - CloudAuth instance
|
|
16
|
+
* @param {object} linkedinAutomation - Legacy LinkedInAutomation instance (optional)
|
|
17
|
+
*/
|
|
18
|
+
export function syncTokensToAutomation(session, socketId, appState, cloudAuth, linkedinAutomation = null) {
|
|
19
|
+
if (!session?.tokens || !session?.user) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If socketId provided, sync to specific process
|
|
24
|
+
if (socketId && appState) {
|
|
25
|
+
const process = appState.getProcess(socketId);
|
|
26
|
+
if (process && process.automation && process.automation.isRunning) {
|
|
27
|
+
const hadExpiredToken = process.automation._currentAccessToken &&
|
|
28
|
+
cloudAuth.isTokenExpired(process.automation._currentAccessToken);
|
|
29
|
+
|
|
30
|
+
process.automation._currentAccessToken = session.tokens.idToken;
|
|
31
|
+
process.automation._currentRefreshToken = session.tokens.refreshToken;
|
|
32
|
+
|
|
33
|
+
if (hadExpiredToken) {
|
|
34
|
+
console.log(`[TokenSync] Synced refreshed tokens to process ${socketId} for user:`, session.user.email);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} else if (linkedinAutomation) {
|
|
38
|
+
// Legacy: sync to single automation instance
|
|
39
|
+
if (linkedinAutomation.isRunning &&
|
|
40
|
+
linkedinAutomation._currentUserEmail === session.user.email) {
|
|
41
|
+
const hadExpiredToken = linkedinAutomation._currentAccessToken &&
|
|
42
|
+
cloudAuth.isTokenExpired(linkedinAutomation._currentAccessToken);
|
|
43
|
+
|
|
44
|
+
linkedinAutomation._currentAccessToken = session.tokens.idToken;
|
|
45
|
+
linkedinAutomation._currentRefreshToken = session.tokens.refreshToken;
|
|
46
|
+
|
|
47
|
+
if (hadExpiredToken) {
|
|
48
|
+
console.log('[TokenSync] Synced refreshed tokens to LinkedInAutomation for user:', session.user.email);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sync tokens to all processes for a specific user
|
|
56
|
+
*
|
|
57
|
+
* @param {object} session - Express session object
|
|
58
|
+
* @param {string} userEmail - User email address
|
|
59
|
+
* @param {object} appState - AppState instance
|
|
60
|
+
* @param {object} cloudAuth - CloudAuth instance
|
|
61
|
+
* @param {object} linkedinAutomation - Legacy LinkedInAutomation instance (optional)
|
|
62
|
+
*/
|
|
63
|
+
export function syncTokensToAllUserProcesses(session, userEmail, appState, cloudAuth, linkedinAutomation = null) {
|
|
64
|
+
if (!userEmail || !appState) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const userProcesses = appState.getProcessesByUser(userEmail);
|
|
69
|
+
userProcesses.forEach(process => {
|
|
70
|
+
syncTokensToAutomation(session, process.socketId, appState, cloudAuth);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Also sync to legacy instance if provided
|
|
74
|
+
if (linkedinAutomation) {
|
|
75
|
+
syncTokensToAutomation(session, null, null, cloudAuth, linkedinAutomation);
|
|
76
|
+
}
|
|
77
|
+
}
|