@ubaidbinwaris/linkedin 1.1.0 → 1.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubaidbinwaris/linkedin",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -28,4 +28,46 @@ async function detectCheckpoint(page) {
28
28
  }
29
29
  }
30
30
 
31
- module.exports = { detectCheckpoint };
31
+ /**
32
+ * Handles mobile verification/app approval prompts.
33
+ * Polls for success (feed navigation) for a set duration.
34
+ * @param {Page} page
35
+ * @returns {Promise<boolean>} true if resolved, false if timed out/failed
36
+ */
37
+ async function handleMobileVerification(page) {
38
+ try {
39
+ const isMobileVerif = await page.evaluate(() => {
40
+ const text = document.body.innerText;
41
+ // User checks: "Check your LinkedIn app", "Open your LinkedIn app", "Approve the sign-in"
42
+ return text.includes("Check your LinkedIn app") ||
43
+ text.includes("Open your LinkedIn app") ||
44
+ text.includes("Tap Yes on the prompt") ||
45
+ text.includes("verification request to your device") ||
46
+ text.includes("Approve the sign-in");
47
+ });
48
+
49
+ if (!isMobileVerif) return false;
50
+
51
+ logger.warn("ACTION REQUIRED: Check your LinkedIn app on your phone! Waiting 2 minutes...");
52
+
53
+ try {
54
+ // Poll for feed URL for 120 seconds
55
+ await page.waitForFunction(() => {
56
+ return window.location.href.includes("/feed") ||
57
+ document.querySelector('.global-nav__search');
58
+ }, { timeout: 120000 });
59
+
60
+ logger.info("Mobile verification successful! Resuming...");
61
+ return true; // Resolved
62
+ } catch (timeoutErr) {
63
+ logger.warn("Mobile verification timed out (No approval detected).");
64
+ return false;
65
+ }
66
+
67
+ } catch (e) {
68
+ logger.warn(`Mobile verification check failed: ${e.message}`);
69
+ return false;
70
+ }
71
+ }
72
+
73
+ module.exports = { detectCheckpoint, handleMobileVerification };
@@ -1,13 +1,19 @@
1
- const { chromium } = require("playwright");
2
1
  const logger = require("../utils/logger");
3
- const { waitForUserResume } = require("../utils/terminal");
4
- const { loadSession, saveSession } = require("../session/sessionManager");
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
- * Main authentication entry point
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,110 @@ async function loginToLinkedIn(options = {}, credentials = null) {
17
23
  throw new Error("Missing LinkedIn credentials.");
18
24
  }
19
25
 
20
- const browser = await createBrowser(options);
21
- const context = await createContext(browser, email);
22
- const page = await context.newPage();
23
-
24
- page.setDefaultTimeout(30000);
25
-
26
- try {
27
- // Try session-based login
28
- await page.goto(LINKEDIN_FEED, { waitUntil: "domcontentloaded" });
29
-
30
- if (await isLoggedIn(page)) {
31
- logger.info(`Session valid for ${email}`);
32
- return { browser, context, page };
33
- }
34
-
35
- logger.info(`Session invalid. Performing credential login for ${email}`);
36
- await performCredentialLogin(page, email, password);
37
-
38
- await handleCheckpoint(page, options);
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
- if (btn) {
126
- btn.click();
127
- return true;
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
- if (clicked) {
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
- // Check if resolved
137
- if (!(await detectCheckpoint(page))) {
138
- logger.info("Checkpoint resolved via button click!");
139
- return;
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
+ // Attempt to handle mobile verification (2 min wait)
72
+ // If it returns true, it means we are now on the feed (resolved)
73
+ if (await handleMobileVerification(page)) {
74
+ // success, assume logged in
75
+ } else {
76
+ // Failed mobile verification.
77
+ // User Request: "otherwise after some delay using browser opens the browser and fill the form"
78
+
79
+ if (options.headless) {
80
+ logger.info("[Fallback] Mobile verification failed. Switching to VISIBLE browser for manual intervention...");
81
+ await browser.close();
82
+
83
+ // RE-LAUNCH in Visible Mode
84
+ const visibleBrowser = await createBrowser({ ...options, headless: false });
85
+ const visibleContext = await createContext(visibleBrowser, email);
86
+ const visiblePage = await visibleContext.newPage();
87
+ visiblePage.setDefaultTimeout(60000); // More time for manual interaction
88
+
89
+ try {
90
+ logger.info("[Fallback] Filling credentials in visible browser...");
91
+ await performCredentialLogin(visiblePage, email, password);
92
+
93
+ // Now wait for success (Feed)
94
+ logger.info("[Fallback] Waiting for user to complete login manually...");
95
+ await visiblePage.waitForSelector(".global-nav__search", { timeout: 120000 });
96
+
97
+ if (await isLoggedIn(visiblePage)) {
98
+ logger.info(`[${email}] Manual Fallback Successful ✅`);
99
+ await saveSession(visibleContext, email, true);
100
+ return { browser: visibleBrowser, context: visibleContext, page: visiblePage };
101
+ }
102
+ } catch (fallbackErr) {
103
+ logger.error(`[Fallback] Manual intervention failed or timed out: ${fallbackErr.message}`);
104
+ await visibleBrowser.close();
105
+ throw new Error("CHECKPOINT_DETECTED_M"); // M for manual failed
106
+ }
107
+ }
108
+
109
+ const screenshotPath = `checkpoint_${email}_${Date.now()}.png`;
110
+ await page.screenshot({ path: screenshotPath });
111
+ logger.warn(`[${email}] Checkpoint detected! Screenshot saved: ${screenshotPath}`);
112
+ throw new Error("CHECKPOINT_DETECTED");
113
+ }
140
114
  }
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
115
 
157
- if (isMobileVerif) {
158
- logger.info("Mobile verification detected. Waiting 2 minutes for manual approval on device...");
159
-
160
- try {
161
- // Poll for feed URL for 120 seconds
162
- // We use a loop or waitForFunction
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
- }
116
+ if (await isLoggedIn(page)) {
117
+ logger.info(`[${email}] Login successful ✅`);
118
+ await saveSession(context, email, true); // Save with validation = true
119
+ return { browser, context, page };
120
+ } else {
121
+ throw new Error("LOGIN_FAILED: Could not verify session after login attempt.");
173
122
  }
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
123
 
195
- return (
196
- url.includes("checkpoint") ||
197
- url.includes("challenge") ||
198
- url.includes("verification") ||
199
- url.includes("consumer-login/error")
200
- );
124
+ } catch (error) {
125
+ logger.error(`[${email}] Login process failed: ${error.message}`);
126
+ await browser.close();
127
+ throw error;
128
+ }
129
+ });
201
130
  }
202
131
 
132
+ module.exports = loginToLinkedIn;