@ubaidbinwaris/linkedin 1.1.0 → 1.1.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.
- package/package.json +1 -1
- package/src/login/login.js +69 -178
package/package.json
CHANGED
package/src/login/login.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
const { chromium } = require("playwright");
|
|
2
1
|
const logger = require("../utils/logger");
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
2
|
+
const { saveSession, SessionLock } = require("../session/sessionManager");
|
|
3
|
+
const { createBrowser, createContext } = require("../browser/launcher");
|
|
4
|
+
const { detectCheckpoint } = require("../auth/checkpoint");
|
|
5
|
+
const { performCredentialLogin, isLoggedIn } = require("../auth/actions");
|
|
6
|
+
const { randomDelay } = require("../utils/time");
|
|
5
7
|
|
|
6
8
|
const LINKEDIN_FEED = "https://www.linkedin.com/feed/";
|
|
7
|
-
const LINKEDIN_LOGIN = "https://www.linkedin.com/login";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Deterministic Login Flow wrapped in SessionLock
|
|
12
|
+
* 1. Acquire Lock (queues if busy)
|
|
13
|
+
* 2. Launch Browser
|
|
14
|
+
* 3. Load Session -> Check needsValidation
|
|
15
|
+
* 4. Login/Verify
|
|
16
|
+
* 5. Save Session (with timestamp)
|
|
11
17
|
*/
|
|
12
18
|
async function loginToLinkedIn(options = {}, credentials = null) {
|
|
13
19
|
const email = credentials?.username || process.env.LINKEDIN_EMAIL;
|
|
@@ -17,186 +23,71 @@ async function loginToLinkedIn(options = {}, credentials = null) {
|
|
|
17
23
|
throw new Error("Missing LinkedIn credentials.");
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!(await isLoggedIn(page))) {
|
|
41
|
-
throw new Error("Login failed. Could not verify authenticated state.");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
await saveSession(context, email);
|
|
45
|
-
logger.info("Session saved successfully.");
|
|
46
|
-
|
|
47
|
-
return { browser, context, page };
|
|
48
|
-
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(`Login process failed: ${error.message}`);
|
|
51
|
-
await browser.close();
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
module.exports = loginToLinkedIn;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
async function createBrowser(options) {
|
|
60
|
-
return chromium.launch({
|
|
61
|
-
headless: options.headless ?? false,
|
|
62
|
-
slowMo: options.slowMo ?? 50,
|
|
63
|
-
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function createContext(browser, email) {
|
|
68
|
-
const storedContext = await loadSession(browser, email);
|
|
69
|
-
|
|
70
|
-
if (storedContext) {
|
|
71
|
-
logger.info("Loaded stored session.");
|
|
72
|
-
return storedContext;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
logger.info("Creating new browser context.");
|
|
76
|
-
return browser.newContext();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
async function isLoggedIn(page) {
|
|
81
|
-
try {
|
|
82
|
-
await page.waitForSelector(".global-nav__search", { timeout: 10000 });
|
|
83
|
-
return true;
|
|
84
|
-
} catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
async function performCredentialLogin(page, email, password) {
|
|
91
|
-
await page.goto(LINKEDIN_LOGIN, { waitUntil: "domcontentloaded" });
|
|
92
|
-
|
|
93
|
-
await page.fill('input[name="session_key"]', email);
|
|
94
|
-
await page.fill('input[name="session_password"]', password);
|
|
95
|
-
|
|
96
|
-
await Promise.all([
|
|
97
|
-
page.waitForNavigation({ waitUntil: "domcontentloaded" }),
|
|
98
|
-
page.click('button[type="submit"]')
|
|
99
|
-
]);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
async function handleCheckpoint(page, options) {
|
|
104
|
-
// Initial check
|
|
105
|
-
if (!(await detectCheckpoint(page))) return;
|
|
106
|
-
|
|
107
|
-
logger.warn("Checkpoint detected.");
|
|
108
|
-
|
|
109
|
-
if (options.headless) {
|
|
110
|
-
logger.info("Headless mode detected. Attempting auto-resolution...");
|
|
111
|
-
|
|
112
|
-
// ---------------------------------------------------------
|
|
113
|
-
// STRATEGY 1: Click Simple Buttons (Yes, Skip, Continue)
|
|
114
|
-
// ---------------------------------------------------------
|
|
115
|
-
try {
|
|
116
|
-
const clicked = await page.evaluate(() => {
|
|
117
|
-
const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="submit"], input[type="button"]'));
|
|
118
|
-
const targetText = ['Yes', 'Skip', 'Not now', 'Continue', 'Sign in', 'Verify', 'Let’s do it', 'Next'];
|
|
119
|
-
|
|
120
|
-
const btn = candidates.find(b => {
|
|
121
|
-
const text = (b.innerText || b.value || '').trim();
|
|
122
|
-
return targetText.some(t => text.includes(t));
|
|
123
|
-
});
|
|
26
|
+
// Wrap entire process in lock
|
|
27
|
+
return SessionLock.withLoginLock(email, async () => {
|
|
28
|
+
const browser = await createBrowser(options);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// ----------------------------
|
|
32
|
+
// STEP 1: Load Session & Context
|
|
33
|
+
// ----------------------------
|
|
34
|
+
const context = await createContext(browser, email);
|
|
35
|
+
const page = await context.newPage();
|
|
36
|
+
page.setDefaultTimeout(30000);
|
|
37
|
+
|
|
38
|
+
// Check validation cache
|
|
39
|
+
// needsValidation is attached to context in sectionManager
|
|
40
|
+
if (context.needsValidation === false) {
|
|
41
|
+
logger.info(`[${email}] Session validation cached. Skipping feed check.`);
|
|
42
|
+
await page.goto(LINKEDIN_FEED, { waitUntil: "domcontentloaded" });
|
|
43
|
+
return { browser, context, page };
|
|
44
|
+
}
|
|
124
45
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
46
|
+
// ----------------------------
|
|
47
|
+
// STEP 2: Verify Session (if needed)
|
|
48
|
+
// ----------------------------
|
|
49
|
+
logger.info(`[${email}] Verifying session...`);
|
|
50
|
+
await page.goto(LINKEDIN_FEED, { waitUntil: "domcontentloaded" });
|
|
51
|
+
await randomDelay(1000, 2000);
|
|
52
|
+
|
|
53
|
+
if (await isLoggedIn(page)) {
|
|
54
|
+
logger.info(`[${email}] Session valid ✅`);
|
|
55
|
+
// Update validation timestamp
|
|
56
|
+
await saveSession(context, email, true);
|
|
57
|
+
return { browser, context, page };
|
|
128
58
|
}
|
|
129
|
-
return false;
|
|
130
|
-
});
|
|
131
59
|
|
|
132
|
-
|
|
133
|
-
logger.info("Clicked a resolution button. Waiting for navigation...");
|
|
134
|
-
await page.waitForTimeout(3000);
|
|
60
|
+
logger.info(`[${email}] Session invalid. Attempting credential login...`);
|
|
135
61
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
62
|
+
// ----------------------------
|
|
63
|
+
// STEP 3: Credential Login
|
|
64
|
+
// ----------------------------
|
|
65
|
+
await performCredentialLogin(page, email, password);
|
|
66
|
+
|
|
67
|
+
// ----------------------------
|
|
68
|
+
// STEP 4: Verify & Fail Fast
|
|
69
|
+
// ----------------------------
|
|
70
|
+
if (await detectCheckpoint(page)) {
|
|
71
|
+
const screenshotPath = `checkpoint_${email}_${Date.now()}.png`;
|
|
72
|
+
await page.screenshot({ path: screenshotPath });
|
|
73
|
+
logger.warn(`[${email}] Checkpoint detected! Screenshot saved: ${screenshotPath}`);
|
|
74
|
+
throw new Error("CHECKPOINT_DETECTED");
|
|
140
75
|
}
|
|
141
|
-
}
|
|
142
|
-
} catch (e) {
|
|
143
|
-
logger.warn(`Auto-resolve button click failed: ${e.message}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------
|
|
147
|
-
// STRATEGY 2: Mobile App Verification (Wait & Poll)
|
|
148
|
-
// ---------------------------------------------------------
|
|
149
|
-
try {
|
|
150
|
-
const isMobileVerif = await page.evaluate(() => {
|
|
151
|
-
const text = document.body.innerText;
|
|
152
|
-
return text.includes("Open your LinkedIn app") ||
|
|
153
|
-
text.includes("Tap Yes on the prompt") ||
|
|
154
|
-
text.includes("verification request to your device");
|
|
155
|
-
});
|
|
156
76
|
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await page.waitForFunction(() => {
|
|
164
|
-
return window.location.href.includes("/feed") ||
|
|
165
|
-
document.querySelector('.global-nav__search');
|
|
166
|
-
}, { timeout: 120000 });
|
|
167
|
-
|
|
168
|
-
logger.info("Mobile verification successful! Resuming...");
|
|
169
|
-
return;
|
|
170
|
-
} catch (timeoutErr) {
|
|
171
|
-
logger.warn("Mobile verification timed out.");
|
|
172
|
-
}
|
|
77
|
+
if (await isLoggedIn(page)) {
|
|
78
|
+
logger.info(`[${email}] Login successful ✅`);
|
|
79
|
+
await saveSession(context, email, true); // Save with validation = true
|
|
80
|
+
return { browser, context, page };
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error("LOGIN_FAILED: Could not verify session after login attempt.");
|
|
173
83
|
}
|
|
174
|
-
} catch (e) {
|
|
175
|
-
logger.warn(`Mobile verification check failed: ${e.message}`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Re-check after attempts
|
|
179
|
-
if (await detectCheckpoint(page)) {
|
|
180
|
-
throw new Error("Checkpoint detected in headless mode and auto-resolution failed.");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
} else {
|
|
184
|
-
// Visible mode
|
|
185
|
-
logger.warn("Verification required. Please complete manually.");
|
|
186
|
-
await waitForUserResume(
|
|
187
|
-
"Complete verification in browser, then press ENTER..."
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async function detectCheckpoint(page) {
|
|
193
|
-
const url = page.url().toLowerCase();
|
|
194
84
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logger.error(`[${email}] Login process failed: ${error.message}`);
|
|
87
|
+
await browser.close();
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
201
91
|
}
|
|
202
92
|
|
|
93
|
+
module.exports = loginToLinkedIn;
|