@ubaidbinwaris/linkedin 1.0.12 → 1.0.14

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/login/login.js +153 -411
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubaidbinwaris/linkedin",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,460 +1,202 @@
1
+ const { chromium } = require("playwright");
1
2
  const logger = require("../utils/logger");
2
- // Use playwright-extra with the stealth plugin
3
- const { chromium } = require("playwright-extra");
4
- const stealth = require("puppeteer-extra-plugin-stealth")();
5
- chromium.use(stealth);
6
-
7
3
  const { waitForUserResume } = require("../utils/terminal");
8
- const { sessionExists, saveSession, loadSession } = require("../session/sessionManager");
9
- const { randomDelay } = require("../utils/time");
4
+ const { loadSession, saveSession } = require("../session/sessionManager");
5
+
6
+ const LINKEDIN_FEED = "https://www.linkedin.com/feed/";
7
+ const LINKEDIN_LOGIN = "https://www.linkedin.com/login";
10
8
 
11
9
  /**
12
- * Validates that necessary environment variables are set.
13
- * @throws {Error} If credentials are missing.
10
+ * Main authentication entry point
14
11
  */
15
- function validateCredentials() {
16
- if (!process.env.LINKEDIN_EMAIL || !process.env.LINKEDIN_PASSWORD) {
17
- throw new Error("Missing LINKEDIN_EMAIL or LINKEDIN_PASSWORD in environment variables.");
12
+ async function loginToLinkedIn(options = {}, credentials = null) {
13
+ const email = credentials?.username || process.env.LINKEDIN_EMAIL;
14
+ const password = credentials?.password || process.env.LINKEDIN_PASSWORD;
15
+
16
+ if (!email || !password) {
17
+ throw new Error("Missing LinkedIn credentials.");
18
18
  }
19
- }
20
19
 
21
- /**
22
- * Detects if a checkpoint/verification is triggered.
23
- * @param {import('playwright').Page} page
24
- * @returns {Promise<boolean>}
25
- */
26
- async function detectCheckpoint(page) {
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
+
27
26
  try {
28
- const url = page.url();
29
- if (
30
- url.includes("checkpoint") ||
31
- url.includes("challenge") ||
32
- url.includes("verification") ||
33
- url.includes("consumer-login/error")
34
- ) {
35
- return true;
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 };
36
33
  }
37
34
 
38
- // Also check for specific checkpoint elements if URL check isn't enough
39
- // This is a non-blocking check with a short timeout
40
- try {
41
- await page.waitForSelector("h1:has-text('Security Verification')", { timeout: 1000 });
42
- return true;
43
- } catch (e) {
44
- // Element not found, likely no checkpoint
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.");
45
42
  }
46
43
 
47
- return false;
44
+ await saveSession(context, email);
45
+ logger.info("Session saved successfully.");
46
+
47
+ return { browser, context, page };
48
+
48
49
  } catch (error) {
49
- logger.error(`Error during checkpoint detection: ${error.message}`);
50
- return false;
50
+ logger.error(`Login process failed: ${error.message}`);
51
+ await browser.close();
52
+ throw error;
51
53
  }
52
54
  }
53
55
 
54
- /**
55
- * Generates random viewport dimensions.
56
- */
57
- function getRandomViewport() {
58
- const width = 1280 + Math.floor(Math.random() * 640); // 1280 - 1920
59
- const height = 720 + Math.floor(Math.random() * 360); // 720 - 1080
60
- return { width, height };
61
- }
56
+ module.exports = loginToLinkedIn;
62
57
 
63
- /**
64
- * Generates a random User-Agent string (simplified for now, ideally use a library).
65
- */
66
- function getRandomUserAgent() {
67
- const versions = ["120.0.0.0", "121.0.0.0", "122.0.0.0"];
68
- const version = versions[Math.floor(Math.random() * versions.length)];
69
- return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`;
70
- }
71
58
 
72
- /**
73
- * Main login function.
74
- * @param {Object} options - Launch options for the browser.
75
- * @param {boolean} [options.headless=false] - Whether to run in headless mode.
76
- * @param {number} [options.slowMo=50] - Slow motion delay in ms.
77
- * @param {string} [options.proxy] - Optional proxy server URL.
78
- */
79
- /**
80
- * Main login function.
81
- * @param {Object} options - Launch options for the browser.
82
- * @param {boolean} [options.headless=false] - Whether to run in headless mode.
83
- * @param {number} [options.slowMo=50] - Slow motion delay in ms.
84
- * @param {string} [options.proxy] - Optional proxy server URL.
85
- * @param {Function} [options.onCheckpoint] - Callback when verification is needed in headless mode.
86
- * @param {Object} [credentials] - Optional credentials object { username, password }
87
- */
88
- async function loginToLinkedIn(options = {}, credentials = null) {
89
- logger.info("Starting LinkedIn login process with stealth mode...");
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
+ }
90
66
 
91
- // Determine credentials
92
- const email = credentials?.username || process.env.LINKEDIN_EMAIL;
93
- const password = credentials?.password || process.env.LINKEDIN_PASSWORD;
67
+ async function createContext(browser, email) {
68
+ const storedContext = await loadSession(browser, email);
94
69
 
95
- if (!email || !password) {
96
- const errorMsg = "Missing credentials. Provide them in arguments or set LINKEDIN_EMAIL/LINKEDIN_PASSWORD env vars.";
97
- logger.error(errorMsg);
98
- throw new Error(errorMsg);
70
+ if (storedContext) {
71
+ logger.info("Loaded stored session.");
72
+ return storedContext;
99
73
  }
100
74
 
101
- const launchOptions = {
102
- headless: options.headless !== undefined ? options.headless : false,
103
- slowMo: options.slowMo || 50,
104
- args: [
105
- "--no-sandbox",
106
- "--disable-setuid-sandbox",
107
- "--disable-blink-features=AutomationControlled" // Extra stealth
108
- ],
109
- ...options,
110
- };
111
-
112
- logger.info(`Launching browser for user: ${email}...`);
113
- const browser = await chromium.launch(launchOptions);
114
-
115
- let context;
116
- let page;
117
-
118
- const contextOptions = {
119
- userAgent: getRandomUserAgent(),
120
- viewport: getRandomViewport(),
121
- locale: 'en-US',
122
- timezoneId: 'America/New_York', // Align with proxy if used, otherwise standard
123
- permissions: ['geolocation'],
124
- ignoreHTTPSErrors: true,
125
- };
75
+ logger.info("Creating new browser context.");
76
+ return browser.newContext();
77
+ }
78
+
126
79
 
80
+ async function isLoggedIn(page) {
127
81
  try {
128
- // -----------------------------
129
- // STEP 1: Try Using Saved Session
130
- // -----------------------------
131
- logger.info(`Checking for saved session for ${email}...`);
132
- context = await loadSession(browser, contextOptions, email);
133
-
134
- if (context) {
135
- logger.info("Session stored context created.");
136
- } else {
137
- logger.info("No valid session found. Starting fresh context.");
138
- context = await browser.newContext(contextOptions);
139
- }
82
+ await page.waitForSelector(".global-nav__search", { timeout: 10000 });
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
140
88
 
141
- page = await context.newPage();
142
89
 
143
- // Set a default timeout for all actions
144
- page.setDefaultTimeout(30000);
90
+ async function performCredentialLogin(page, email, password) {
91
+ await page.goto(LINKEDIN_LOGIN, { waitUntil: "domcontentloaded" });
145
92
 
146
- logger.info("Navigating to LinkedIn feed...");
147
- await page.goto("https://www.linkedin.com/feed/", {
148
- waitUntil: "domcontentloaded",
149
- });
93
+ await page.fill('input[name="session_key"]', email);
94
+ await page.fill('input[name="session_password"]', password);
150
95
 
151
- // Human-like pause
152
- await randomDelay(2000, 4000);
96
+ await Promise.all([
97
+ page.waitForNavigation({ waitUntil: "domcontentloaded" }),
98
+ page.click('button[type="submit"]')
99
+ ]);
100
+ }
153
101
 
154
- // Check for checkpoint immediately after navigation
155
- if (await detectCheckpoint(page)) {
156
- if (launchOptions.headless) {
157
- logger.warn("Checkpoint detected in headless mode.");
158
-
159
- // Attempt to resolve simple checkpoints (e.g. "Yes, it's me", "Skip") automatically
160
- try {
161
- logger.info("Attempting to resolve simple checkpoint headlessly...");
162
- const simpleResolved = await page.evaluate(async () => {
163
- // Find buttons, links, or elements with button role
164
- const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="submit"], input[type="button"]'));
165
- const targetText = ['Yes', 'Skip', 'Not now', 'Continue', 'Sign in', 'Verify', 'Let’s do it', 'Next'];
166
-
167
- // Find a candidate with one of these texts
168
- const btn = candidates.find(b => {
169
- const text = (b.innerText || b.value || '').trim();
170
- return targetText.some(t => text.includes(t));
171
- });
172
-
173
- if (btn) {
174
- btn.click();
175
- return true;
176
- }
177
- return false;
178
- });
179
-
180
- if (simpleResolved) {
181
- logger.info("Clicked a resolution button. Waiting to see if it clears...");
182
- await randomDelay(2000, 4000);
183
- if (!(await detectCheckpoint(page))) {
184
- logger.info("Checkpoint resolved headlessly! Proceeding...");
185
- try {
186
- await page.waitForURL("**/feed**", { timeout: 10000 });
187
- return { browser, context, page };
188
- } catch (e) {
189
- logger.warn("Resolved checkpoint but feed did not load. Continuing...");
190
- }
191
- }
192
- } else {
193
- // Check for Mobile Verification ("Open your LinkedIn app")
194
- const isMobileVerif = await page.evaluate(() => {
195
- const text = document.body.innerText;
196
- return text.includes("Open your LinkedIn app") ||
197
- text.includes("Tap Yes on the prompt") ||
198
- text.includes("verification request to your device");
199
- });
200
-
201
- if (isMobileVerif) {
202
- logger.info("Mobile verification detected (Open App / Tap Yes).");
203
- logger.info("Waiting 2 minutes for you to approve on your device...");
204
-
205
- try {
206
- // Poll for feed URL for 120 seconds
207
- await page.waitForFunction(() => {
208
- return window.location.href.includes("/feed") ||
209
- document.querySelector('.global-nav__search');
210
- }, { timeout: 120000 });
211
-
212
- logger.info("Mobile verification successful! Resuming...");
213
- return { browser, context, page };
214
- } catch (err) {
215
- logger.warn("Mobile verification timed out. Falling back to visible mode.");
216
- }
217
- }
218
- }
219
- } catch (err) {
220
- logger.warn(`Failed to auto-resolve checkpoint: ${err.message}`);
221
- }
222
102
 
103
+ async function handleCheckpoint(page, options) {
104
+ // Initial check
105
+ if (!(await detectCheckpoint(page))) return;
223
106
 
224
- if (options.onCheckpoint && typeof options.onCheckpoint === 'function') {
225
- logger.info("Triggering onCheckpoint callback...");
226
- await options.onCheckpoint();
227
- logger.info("onCheckpoint resolved. Retrying login...");
228
- await browser.close();
229
- return loginToLinkedIn(options, credentials);
230
- }
107
+ logger.warn("Checkpoint detected.");
231
108
 
232
- logger.info("Switching to visible mode for manual verification...");
233
-
234
- // await waitForUserResume("Press ENTER to open a visible browser to verify your account...");
235
- logger.info("Automatically launching visible browser for verification...");
236
-
237
- logger.info("Closing headless browser...");
238
- await browser.close();
109
+ if (options.headless) {
110
+ logger.info("Headless mode detected. Attempting auto-resolution...");
239
111
 
240
- logger.info("Launching visible browser for verification...");
241
- // Call recursively in visible mode
242
- // We pass the same options but force headless: false
243
- const visibleInstance = await loginToLinkedIn({ ...options, headless: false }, { username: email, password });
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'];
244
119
 
245
- // Once the visible instance returns, it means login was successful and session is saved.
246
- logger.info("Verification successful in visible mode.");
247
- logger.info("Closing visible browser and resuming headless session...");
248
- await visibleInstance.browser.close();
120
+ const btn = candidates.find(b => {
121
+ const text = (b.innerText || b.value || '').trim();
122
+ return targetText.some(t => text.includes(t));
123
+ });
124
+
125
+ if (btn) {
126
+ btn.click();
127
+ return true;
128
+ }
129
+ return false;
130
+ });
131
+
132
+ if (clicked) {
133
+ logger.info("Clicked a resolution button. Waiting for navigation...");
134
+ await page.waitForTimeout(3000);
249
135
 
250
- // Restart the original headless request.
251
- // It should now find the valid session and proceed without checkpoints.
252
- return loginToLinkedIn(options, { username: email, password });
253
-
254
- } else {
255
- logger.warn("Checkpoint detected immediately. Manual verification required.");
256
- if (options.onCheckpoint && typeof options.onCheckpoint === 'function') {
257
- await options.onCheckpoint();
258
- } else {
259
- await waitForUserResume("Complete verification in the opened browser, then press ENTER here to continue...");
260
- }
136
+ // Check if resolved
137
+ if (!(await detectCheckpoint(page))) {
138
+ logger.info("Checkpoint resolved via button click!");
139
+ return;
140
+ }
261
141
  }
142
+ } catch (e) {
143
+ logger.warn(`Auto-resolve button click failed: ${e.message}`);
262
144
  }
263
145
 
264
- const { VALIDATION_SELECTORS } = require("../config");
265
-
266
- // Verify Session Validity
146
+ // ---------------------------------------------------------
147
+ // STRATEGY 2: Mobile App Verification (Wait & Poll)
148
+ // ---------------------------------------------------------
267
149
  try {
268
- // Check for any of the validation selectors
269
- const isLoggedIn = await Promise.race([
270
- Promise.any(VALIDATION_SELECTORS.map(selector =>
271
- page.waitForSelector(selector, { timeout: 15000 }).then(() => true)
272
- )),
273
- page.waitForSelector('.login-form, #username, input[name="session_key"]', { timeout: 5000 }).then(() => false)
274
- ]).catch(() => false);
275
-
276
- if (isLoggedIn) {
277
- logger.info("Session is valid. Login successful.");
278
- return { browser, context, page };
279
- } else {
280
- logger.info("Session invalid or redirected to login page.");
281
- }
282
- } catch (err) {
283
- logger.info("Could not verify session state. Proceeding to credential login.");
284
- }
285
-
286
- // CRITICAL FIX: If session was invalid, we MUST close the current context
287
- // and start fresh. Otherwise, retained cookies might cause redirect loops
288
- // (e.g. we go to /login, but LinkedIn sees cookies and redirects to /feed,
289
- // so we can't find the email input).
290
- logger.info("Closing invalid/expired session context...");
291
- await context.close();
292
-
293
- logger.info("Starting fresh context for credential login...");
294
- context = await browser.newContext(contextOptions);
295
- page = await context.newPage();
296
- page.setDefaultTimeout(30000);
297
-
298
- // -----------------------------
299
- // STEP 2: Credential Login
300
- // -----------------------------
301
- logger.info("Proceeding to credential login...");
302
-
303
- if (!page.url().includes("login") && !page.url().includes("uas/request-password-reset")) {
304
- await page.goto("https://www.linkedin.com/login", { waitUntil: 'domcontentloaded' });
305
- await randomDelay(1000, 2000);
306
- }
307
-
308
- logger.info("Entering credentials...");
309
-
310
- // Simulate human typing
311
- await page.click('input[name="session_key"]');
312
- await randomDelay(500, 1000);
313
- await page.type('input[name="session_key"]', email, { delay: 100 }); // Type with delay
314
-
315
- await randomDelay(1000, 2000);
316
-
317
- await page.click('input[name="session_password"]');
318
- await page.type('input[name="session_password"]', password, { delay: 100 });
319
-
320
- await randomDelay(1000, 2000);
321
-
322
- logger.info("Submitting login form...");
323
- await Promise.all([
324
- page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
325
- page.click('button[type="submit"]')
326
- ]);
327
-
328
- // Check for checkpoint again
329
- if (await detectCheckpoint(page)) {
330
- if (launchOptions.headless) {
331
- logger.warn("Checkpoint detected in headless mode (post-login).");
332
-
333
- // Attempt to resolve simple checkpoints (e.g. "Yes, it's me", "Skip") automatically
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
+
157
+ if (isMobileVerif) {
158
+ logger.info("Mobile verification detected. Waiting 2 minutes for manual approval on device...");
159
+
334
160
  try {
335
- logger.info("Attempting to resolve simple checkpoint headlessly (post-login)...");
336
- const simpleResolved = await page.evaluate(async () => {
337
- // Find buttons, links, or elements with button role
338
- const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="submit"], input[type="button"]'));
339
- const targetText = ['Yes', 'Skip', 'Not now', 'Continue', 'Sign in', 'Verify', 'Let’s do it', 'Next'];
340
-
341
- // Find a candidate with one of these texts
342
- const btn = candidates.find(b => {
343
- const text = (b.innerText || b.value || '').trim();
344
- return targetText.some(t => text.includes(t));
345
- });
346
-
347
- if (btn) {
348
- btn.click();
349
- return true;
350
- }
351
- return false;
352
- });
353
-
354
- if (simpleResolved) {
355
- logger.info("Clicked a resolution button. Waiting to see if it clears...");
356
- await randomDelay(2000, 4000);
357
- if (!(await detectCheckpoint(page))) {
358
- logger.info("Checkpoint resolved headlessly! Proceeding...");
359
- // Re-verify session
360
- try {
361
- await page.waitForURL("**/feed**", { timeout: 10000 });
362
- return { browser, context, page };
363
- } catch (e) {
364
- logger.warn("Resolved checkpoint but feed did not load. Continuing...");
365
- }
366
- }
367
- }
368
- } catch (err) {
369
- logger.warn(`Failed to auto-resolve post-login checkpoint: ${err.message}`);
370
- }
371
-
372
- if (options.onCheckpoint && typeof options.onCheckpoint === 'function') {
373
- logger.info("Triggering onCheckpoint callback...");
374
- await options.onCheckpoint();
375
- logger.info("onCheckpoint resolved. Retrying login...");
376
- await browser.close();
377
- return loginToLinkedIn(options, credentials);
378
- }
379
-
380
- logger.info("Switching to visible mode for manual verification...");
381
-
382
- // await waitForUserResume("Press ENTER to open a visible browser to verify your account...");
383
- logger.info("Automatically launching visible browser for verification...");
384
-
385
- logger.info("Closing headless browser...");
386
- await browser.close();
387
-
388
- logger.info("Launching visible browser for verification...");
389
- const visibleInstance = await loginToLinkedIn({ ...options, headless: false }, { username: email, password });
390
-
391
- logger.info("Verification successful. Resuming headless session...");
392
- await visibleInstance.browser.close();
393
-
394
- return loginToLinkedIn(options, { username: email, password });
395
- } else {
396
- logger.warn("Checkpoint detected after login attempt. Manual verification required.");
397
- if (options.onCheckpoint && typeof options.onCheckpoint === 'function') {
398
- await options.onCheckpoint();
399
- } else {
400
- logger.info("Waiting for manual verification in the opened browser...");
401
- logger.info("Please solve the CAPTCHA/verification. The browser will close automatically when you are redirected to the feed.");
402
-
403
- try {
404
- // Wait for URL to include '/feed' OR any validation selector to appear
161
+ // Poll for feed URL for 120 seconds
162
+ // We use a loop or waitForFunction
405
163
  await page.waitForFunction(() => {
406
164
  return window.location.href.includes("/feed") ||
407
- document.querySelector('.global-nav__search') ||
408
- document.querySelector('#global-nav-typeahead');
409
- }, { timeout: 300000 }); // 5 minutes timeout
410
-
411
- logger.info("Verification detected! resuming...");
412
- } catch (err) {
413
- logger.error("Timeout waiting for manual verification.");
414
- throw new Error("Manual verification timed out.");
415
- }
416
- }
417
- }
418
- }
165
+ document.querySelector('.global-nav__search');
166
+ }, { timeout: 120000 });
419
167
 
420
- // -----------------------------
421
- // Post-Login Verification
422
- // -----------------------------
423
- logger.info("Verifying login success...");
424
- try {
425
- await page.waitForURL("**/feed**", { timeout: 20000 });
426
-
427
- // Wait for at least one validation selector
428
- await Promise.any(VALIDATION_SELECTORS.map(selector =>
429
- page.waitForSelector(selector, { timeout: 15000 })
430
- ));
431
-
432
- logger.info("Login confirmed ✅");
433
-
434
- // Save session state
435
- await saveSession(context, email);
436
- logger.info("Session state saved 💾");
437
-
438
- return { browser, context, page };
439
-
440
- } catch (err) {
441
- logger.error("Login failed or timed out waiting for feed.");
442
- const screenshotPath = `error_login_${Date.now()}.png`;
443
- try {
444
- await page.screenshot({ path: screenshotPath });
445
- logger.info(`Screenshot saved to ${screenshotPath}`);
446
- } catch (opts) {
447
- console.error("Failed to take error screenshot");
168
+ logger.info("Mobile verification successful! Resuming...");
169
+ return;
170
+ } catch (timeoutErr) {
171
+ logger.warn("Mobile verification timed out.");
172
+ }
448
173
  }
449
-
450
- throw new Error("Login failed: Could not reach feed page.");
174
+ } catch (e) {
175
+ logger.warn(`Mobile verification check failed: ${e.message}`);
451
176
  }
452
177
 
453
- } catch (error) {
454
- logger.error(`Critical error in loginToLinkedIn: ${error.message}`);
455
- if (browser) await browser.close();
456
- throw error;
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
+ );
457
189
  }
458
190
  }
459
191
 
460
- module.exports = loginToLinkedIn;
192
+ async function detectCheckpoint(page) {
193
+ const url = page.url().toLowerCase();
194
+
195
+ return (
196
+ url.includes("checkpoint") ||
197
+ url.includes("challenge") ||
198
+ url.includes("verification") ||
199
+ url.includes("consumer-login/error")
200
+ );
201
+ }
202
+