barebrowse 0.5.9 → 0.6.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/CHANGELOG.md +57 -0
- package/README.md +2 -2
- package/barebrowse.context.md +7 -10
- package/mcp-server.js +33 -49
- package/package.json +1 -1
- package/src/chromium.js +5 -4
- package/src/index.js +51 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
Headed fallback is now a per-navigation escape hatch, not a permanent mode switch. Graceful degradation when headed is unavailable.
|
|
6
|
+
|
|
7
|
+
### Switch-back to headless (`src/index.js`)
|
|
8
|
+
- `connect().goto()` in hybrid mode: if currently headed from a previous fallback, kills the headed browser and launches fresh headless before navigating
|
|
9
|
+
- New `currentlyHeaded` runtime state variable tracks actual browser mode (vs `mode` which is user config)
|
|
10
|
+
- `createPage()` stealth decision uses runtime mode (`!currentlyHeaded`) instead of config mode (`mode !== 'headed'`)
|
|
11
|
+
- `createTab()` also uses `currentlyHeaded` for correct stealth application
|
|
12
|
+
|
|
13
|
+
### Graceful degradation (`src/index.js`)
|
|
14
|
+
- `connect().goto()` hybrid fallback wrapped in try/catch — if `launch({ headed: true })` fails (no `$DISPLAY`, no Wayland, CI/Docker), keeps the headless result with `botBlocked: true` and `[BOT CHALLENGE DETECTED]` warning
|
|
15
|
+
- `browse()` hybrid fallback also wrapped in try/catch — same graceful degradation for one-shot browsing
|
|
16
|
+
- No crash on headless-only environments
|
|
17
|
+
|
|
18
|
+
### Flow after changes
|
|
19
|
+
```
|
|
20
|
+
goto(url) in hybrid mode:
|
|
21
|
+
1. If currently headed → kill headed, launch headless, reset currentlyHeaded
|
|
22
|
+
2. Navigate to url
|
|
23
|
+
3. Check bot-blocked
|
|
24
|
+
4. If bot-blocked → TRY launch headed (set currentlyHeaded=true)
|
|
25
|
+
→ CATCH: headed unavailable, keep headless result
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Docs
|
|
29
|
+
- Updated hybrid mode descriptions in barebrowse.context.md, system-state.md, prd.md
|
|
30
|
+
|
|
31
|
+
### Tests
|
|
32
|
+
- All existing tests pass (tests use headless mode, unaffected by hybrid logic)
|
|
33
|
+
|
|
34
|
+
## 0.6.0
|
|
35
|
+
|
|
36
|
+
Self-launching headed fallback. Headed and hybrid modes no longer require a manually-launched browser on port 9222 — barebrowse auto-launches a visible Chromium window via `launch({ headed: true })`.
|
|
37
|
+
|
|
38
|
+
### Headed mode auto-launch (`src/chromium.js`)
|
|
39
|
+
- `launch()` accepts `headed` option — skips `--headless=new` and `--hide-scrollbars` flags
|
|
40
|
+
- Same temp profile, same random port, same CDP parsing, same process return
|
|
41
|
+
|
|
42
|
+
### Hybrid fallback fix (`src/index.js`)
|
|
43
|
+
- All 4 `getDebugUrl(port)` call sites replaced with `launch({ headed: true, proxy })` + `createCDP(browser.wsUrl)`
|
|
44
|
+
- `browse()` headed branch, `browse()` hybrid fallback, `connect()` headed branch, `connect().goto()` hybrid fallback
|
|
45
|
+
- `getDebugUrl` import removed from index.js (still exported from chromium.js for external use)
|
|
46
|
+
- Hybrid mode now actually works — previously it tried to connect to port 9222 which nobody ran
|
|
47
|
+
|
|
48
|
+
### Assess handler simplified (`mcp-server.js`)
|
|
49
|
+
- Removed dual-path `runAssess(headed)` function (~60 lines of broken headed fallback)
|
|
50
|
+
- Assess now uses the session's hybrid mode: if tab is bot-blocked, triggers headed fallback via main page `goto()`, then retries in a new tab
|
|
51
|
+
- One flow, no separate `connect({ mode: 'headed' })` call
|
|
52
|
+
|
|
53
|
+
### Docs
|
|
54
|
+
- Removed all "launch browser with --remote-debugging-port=9222" instructions
|
|
55
|
+
- Updated headed/hybrid mode descriptions across barebrowse.context.md, README.md, system-state.md, prd.md
|
|
56
|
+
|
|
57
|
+
### Tests
|
|
58
|
+
- 71/71 passing — no test changes needed (all tests use headless mode)
|
|
59
|
+
|
|
3
60
|
## 0.5.8
|
|
4
61
|
|
|
5
62
|
Bot challenge detection for all browsing, not just assess.
|
package/README.md
CHANGED
|
@@ -100,8 +100,8 @@ For code examples, API reference, and wiring instructions, see **[barebrowse.con
|
|
|
100
100
|
| Mode | What happens | Best for |
|
|
101
101
|
|------|-------------|----------|
|
|
102
102
|
| **Headless** (default) | Launches a fresh Chromium, no UI | Fast automation, scraping, reading pages |
|
|
103
|
-
| **Headed** |
|
|
104
|
-
| **Hybrid** | Tries headless first,
|
|
103
|
+
| **Headed** | Auto-launches a visible Chromium window | Bot-detected sites, visual debugging, CAPTCHAs |
|
|
104
|
+
| **Hybrid** | Tries headless first, auto-launches headed if blocked | General-purpose agent browsing |
|
|
105
105
|
|
|
106
106
|
## What it handles automatically
|
|
107
107
|
|
package/barebrowse.context.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# barebrowse -- Integration Guide
|
|
2
2
|
|
|
3
3
|
> For AI assistants and developers wiring barebrowse into a project.
|
|
4
|
-
> v0.
|
|
4
|
+
> v0.6.1 | Node.js >= 22 | 0 required deps | MIT
|
|
5
5
|
|
|
6
6
|
## What this is
|
|
7
7
|
|
|
@@ -23,10 +23,8 @@ Three integration paths:
|
|
|
23
23
|
| Mode | What it does | When to use |
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| `headless` (default) | Launches a fresh Chromium, no UI | Scraping, reading, fast automation |
|
|
26
|
-
| `headed` |
|
|
27
|
-
| `hybrid` | Tries headless first,
|
|
28
|
-
|
|
29
|
-
Headed mode requires the browser to be launched with `--remote-debugging-port=9222`.
|
|
26
|
+
| `headed` | Auto-launches a visible Chromium window | Bot-detected sites, debugging, visual tasks |
|
|
27
|
+
| `hybrid` | Tries headless first, headed fallback per-navigation (switches back to headless next time) | General-purpose agent browsing |
|
|
30
28
|
|
|
31
29
|
## Minimal usage: one-shot browse
|
|
32
30
|
|
|
@@ -45,13 +43,12 @@ const snapshot = await browse('https://example.com', {
|
|
|
45
43
|
pruneMode: 'act', // 'act' (interactive elements) | 'read' (all content)
|
|
46
44
|
consent: true, // auto-dismiss cookie consent dialogs
|
|
47
45
|
timeout: 30000, // navigation timeout in ms
|
|
48
|
-
port: 9222, // CDP port for headed/hybrid mode
|
|
49
46
|
});
|
|
50
47
|
```
|
|
51
48
|
|
|
52
49
|
## connect() API
|
|
53
50
|
|
|
54
|
-
`connect(opts)` returns a page handle for interactive sessions. Same opts as `browse()` for mode
|
|
51
|
+
`connect(opts)` returns a page handle for interactive sessions. Same opts as `browse()` for mode. Supports `hybrid` mode — starts headless, auto-launches headed on bot detection (same as `browse()`).
|
|
55
52
|
|
|
56
53
|
| Method | Args | Returns | Notes |
|
|
57
54
|
|---|---|---|---|
|
|
@@ -312,13 +309,13 @@ Useful for agent threshold decisions: "skip sites above score 40", "warn if term
|
|
|
312
309
|
|
|
313
310
|
3. **Pruning modes matter.** `act` mode (default) keeps interactive elements + visible labels. `read` mode keeps all text content. Use `read` for content extraction, `act` for form filling and navigation.
|
|
314
311
|
|
|
315
|
-
4. **Headed mode
|
|
312
|
+
4. **Headed mode auto-launches Chromium.** No need to start a browser manually — barebrowse launches a headed Chromium instance with CDP enabled automatically.
|
|
316
313
|
|
|
317
314
|
5. **Cookie extraction needs unlocked profile.** Chromium cookies are AES-encrypted with a keyring key. If Chromium is running, the profile may be locked. Firefox cookies are plaintext and always accessible.
|
|
318
315
|
|
|
319
|
-
6. **Hybrid mode
|
|
316
|
+
6. **Hybrid mode is per-navigation.** If headless is bot-blocked, hybrid kills headless and launches headed for that URL. On the next `goto()`, it switches back to headless automatically. If headed can't launch (no display — CI, Docker), it degrades gracefully with the headless result and a `[BOT CHALLENGE DETECTED]` warning.
|
|
320
317
|
|
|
321
|
-
7. **One page per connect().** Each `connect()` call creates one page.
|
|
318
|
+
7. **One page per connect(), but tabs are supported.** Each `connect()` call creates one page. Use `createTab()` for additional tabs in the same browser.
|
|
322
319
|
|
|
323
320
|
8. **Consent dismiss is best-effort.** It handles 16+ tested sites across 29 languages but novel consent implementations may need manual handling. Disable with `{ consent: false }`.
|
|
324
321
|
|
package/mcp-server.js
CHANGED
|
@@ -286,61 +286,45 @@ async function handleToolCall(name, args) {
|
|
|
286
286
|
if (!assessFn) throw new Error('wearehere is not installed. Run: npm install wearehere');
|
|
287
287
|
const releaseSlot = await acquireAssessSlot();
|
|
288
288
|
try {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
tab = await connect({ mode: 'headed' });
|
|
293
|
-
} else {
|
|
294
|
-
const page = await getPage();
|
|
295
|
-
tab = await page.createTab();
|
|
296
|
-
}
|
|
297
|
-
let timer;
|
|
298
|
-
try {
|
|
299
|
-
const result = await Promise.race([
|
|
300
|
-
(async () => {
|
|
301
|
-
await tab.injectCookies(args.url).catch(() => {});
|
|
302
|
-
return await assessFn(tab, args.url, { timeout: args.timeout, settle: args.settle });
|
|
303
|
-
})(),
|
|
304
|
-
new Promise((_, reject) => {
|
|
305
|
-
timer = setTimeout(() => {
|
|
306
|
-
tab.close().catch(() => {});
|
|
307
|
-
reject(new Error('assess timeout'));
|
|
308
|
-
}, 30000);
|
|
309
|
-
}),
|
|
310
|
-
]);
|
|
311
|
-
clearTimeout(timer);
|
|
312
|
-
const wasBotBlocked = tab.botBlocked;
|
|
313
|
-
await tab.close().catch(() => {});
|
|
314
|
-
return { result, botBlocked: wasBotBlocked };
|
|
315
|
-
} catch (err) {
|
|
316
|
-
clearTimeout(timer);
|
|
317
|
-
await tab.close().catch(() => {});
|
|
318
|
-
throw err;
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// Try headless first
|
|
289
|
+
const page = await getPage();
|
|
290
|
+
const tab = await page.createTab();
|
|
291
|
+
let timer;
|
|
323
292
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
293
|
+
await tab.injectCookies(args.url).catch(() => {});
|
|
294
|
+
const result = await Promise.race([
|
|
295
|
+
assessFn(tab, args.url, { timeout: args.timeout, settle: args.settle }),
|
|
296
|
+
new Promise((_, rej) => { timer = setTimeout(() => rej(new Error('assess timeout')), 30000); }),
|
|
297
|
+
]);
|
|
298
|
+
clearTimeout(timer);
|
|
299
|
+
if (tab.botBlocked) {
|
|
300
|
+
// Bot-blocked — trigger hybrid fallback via main page, retry in new tab
|
|
301
|
+
await tab.close().catch(() => {});
|
|
302
|
+
await page.goto(args.url);
|
|
303
|
+
const tab2 = await page.createTab();
|
|
304
|
+
let timer2;
|
|
327
305
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
306
|
+
await tab2.injectCookies(args.url).catch(() => {});
|
|
307
|
+
const r2 = await Promise.race([
|
|
308
|
+
assessFn(tab2, args.url, { timeout: args.timeout, settle: args.settle }),
|
|
309
|
+
new Promise((_, rej) => { timer2 = setTimeout(() => rej(new Error('assess timeout')), 30000); }),
|
|
310
|
+
]);
|
|
311
|
+
clearTimeout(timer2);
|
|
312
|
+
if (tab2.botBlocked) r2._warning = 'Bot-blocked in both modes. Score may be unreliable.';
|
|
313
|
+
await tab2.close().catch(() => {});
|
|
314
|
+
return JSON.stringify(r2, null, 2);
|
|
315
|
+
} catch (err2) {
|
|
316
|
+
clearTimeout(timer2);
|
|
317
|
+
await tab2.close().catch(() => {});
|
|
318
|
+
throw err2;
|
|
332
319
|
}
|
|
333
320
|
}
|
|
321
|
+
await tab.close().catch(() => {});
|
|
334
322
|
return JSON.stringify(result, null, 2);
|
|
335
323
|
} catch (err) {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
await tab.close().catch(() => {});
|
|
336
326
|
if (isCdpDead(err)) _page = null;
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
const headed = await runAssess(true);
|
|
340
|
-
return JSON.stringify(headed.result, null, 2);
|
|
341
|
-
} catch (retryErr) {
|
|
342
|
-
throw retryErr;
|
|
343
|
-
}
|
|
327
|
+
throw err;
|
|
344
328
|
}
|
|
345
329
|
} finally {
|
|
346
330
|
releaseSlot();
|
|
@@ -366,7 +350,7 @@ async function handleMessage(msg) {
|
|
|
366
350
|
return jsonrpcResponse(id, {
|
|
367
351
|
protocolVersion: '2024-11-05',
|
|
368
352
|
capabilities: { tools: {} },
|
|
369
|
-
serverInfo: { name: 'barebrowse', version: '0.
|
|
353
|
+
serverInfo: { name: 'barebrowse', version: '0.6.0' },
|
|
370
354
|
});
|
|
371
355
|
}
|
|
372
356
|
|
package/package.json
CHANGED
package/src/chromium.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* chromium.js — Find, launch, and connect to Chromium-based browsers.
|
|
3
3
|
*
|
|
4
4
|
* Supports: Chrome, Chromium, Brave, Edge, Vivaldi, Arc, Opera.
|
|
5
|
-
* Modes: headless (launch new), headed (
|
|
5
|
+
* Modes: headless (launch new, no UI), headed (launch new, visible window).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { execSync, spawn } from 'node:child_process';
|
|
@@ -55,11 +55,12 @@ export function findBrowser() {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* Launch a
|
|
58
|
+
* Launch a Chromium instance with CDP enabled.
|
|
59
59
|
* @param {object} [opts]
|
|
60
60
|
* @param {string} [opts.binary] - Path to browser binary (auto-detected if omitted)
|
|
61
61
|
* @param {number} [opts.port=0] - CDP port (0 = random available port)
|
|
62
62
|
* @param {string} [opts.userDataDir] - Browser profile directory
|
|
63
|
+
* @param {boolean} [opts.headed=false] - Launch in headed mode (with visible window)
|
|
63
64
|
* @returns {Promise<{wsUrl: string, process: ChildProcess, port: number}>}
|
|
64
65
|
*/
|
|
65
66
|
export async function launch(opts = {}) {
|
|
@@ -67,7 +68,6 @@ export async function launch(opts = {}) {
|
|
|
67
68
|
const port = opts.port || 0;
|
|
68
69
|
|
|
69
70
|
const args = [
|
|
70
|
-
'--headless=new',
|
|
71
71
|
`--remote-debugging-port=${port}`,
|
|
72
72
|
'--no-first-run',
|
|
73
73
|
'--no-default-browser-check',
|
|
@@ -75,7 +75,8 @@ export async function launch(opts = {}) {
|
|
|
75
75
|
'--disable-sync',
|
|
76
76
|
'--disable-translate',
|
|
77
77
|
'--mute-audio',
|
|
78
|
-
|
|
78
|
+
// Headless-only flags
|
|
79
|
+
...(!opts.headed ? ['--headless=new', '--hide-scrollbars'] : []),
|
|
79
80
|
// Suppress permission prompts (location, notifications, camera, mic, etc.)
|
|
80
81
|
'--disable-notifications',
|
|
81
82
|
'--autoplay-policy=no-user-gesture-required',
|
package/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* const snapshot = await browse('https://example.com');
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { launch
|
|
11
|
+
import { launch } from './chromium.js';
|
|
12
12
|
import { createCDP } from './cdp.js';
|
|
13
13
|
import { formatTree } from './aria.js';
|
|
14
14
|
import { authenticate } from './auth.js';
|
|
@@ -27,7 +27,6 @@ import { applyStealth } from './stealth.js';
|
|
|
27
27
|
* @param {boolean} [opts.cookies=true] - Inject user's cookies (Phase 2)
|
|
28
28
|
* @param {boolean} [opts.prune=true] - Apply ARIA pruning (Phase 2)
|
|
29
29
|
* @param {number} [opts.timeout=30000] - Navigation timeout in ms
|
|
30
|
-
* @param {number} [opts.port] - CDP port for headed mode
|
|
31
30
|
* @returns {Promise<string>} ARIA snapshot text
|
|
32
31
|
*/
|
|
33
32
|
export async function browse(url, opts = {}) {
|
|
@@ -40,9 +39,8 @@ export async function browse(url, opts = {}) {
|
|
|
40
39
|
try {
|
|
41
40
|
// Step 1: Get a CDP connection
|
|
42
41
|
if (mode === 'headed') {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
cdp = await createCDP(wsUrl);
|
|
42
|
+
browser = await launch({ headed: true, proxy: opts.proxy });
|
|
43
|
+
cdp = await createCDP(browser.wsUrl);
|
|
46
44
|
} else {
|
|
47
45
|
// headless or hybrid (start headless)
|
|
48
46
|
browser = await launch({ proxy: opts.proxy });
|
|
@@ -81,17 +79,20 @@ export async function browse(url, opts = {}) {
|
|
|
81
79
|
cdp.close();
|
|
82
80
|
if (browser) { browser.process.kill(); browser = null; }
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
try {
|
|
83
|
+
browser = await launch({ headed: true, proxy: opts.proxy });
|
|
84
|
+
cdp = await createCDP(browser.wsUrl);
|
|
85
|
+
page = await createPage(cdp, false, { viewport: opts.viewport });
|
|
86
|
+
await suppressPermissions(cdp);
|
|
87
|
+
if (opts.cookies !== false) {
|
|
88
|
+
try { await authenticate(page.session, url, { browser: opts.browser }); } catch {}
|
|
89
|
+
}
|
|
90
|
+
await navigate(page, url, timeout);
|
|
91
|
+
if (opts.consent !== false) await dismissConsent(page.session);
|
|
92
|
+
({ tree } = await ariaTree(page));
|
|
93
|
+
} catch {
|
|
94
|
+
// Headed launch failed (no display?) — return headless result as-is
|
|
91
95
|
}
|
|
92
|
-
await navigate(page, url, timeout);
|
|
93
|
-
if (opts.consent !== false) await dismissConsent(page.session);
|
|
94
|
-
({ tree } = await ariaTree(page));
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Step 6: Prune for agent consumption
|
|
@@ -121,7 +122,6 @@ export async function browse(url, opts = {}) {
|
|
|
121
122
|
*
|
|
122
123
|
* @param {object} [opts]
|
|
123
124
|
* @param {'headless'|'headed'|'hybrid'} [opts.mode='headless'] - Browser mode
|
|
124
|
-
* @param {number} [opts.port=9222] - CDP port for headed mode
|
|
125
125
|
* @returns {Promise<object>} Page handle with goto, snapshot, close
|
|
126
126
|
*/
|
|
127
127
|
export async function connect(opts = {}) {
|
|
@@ -130,15 +130,15 @@ export async function connect(opts = {}) {
|
|
|
130
130
|
let cdp;
|
|
131
131
|
|
|
132
132
|
if (mode === 'headed') {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
cdp = await createCDP(wsUrl);
|
|
133
|
+
browser = await launch({ headed: true, proxy: opts.proxy });
|
|
134
|
+
cdp = await createCDP(browser.wsUrl);
|
|
136
135
|
} else {
|
|
137
136
|
browser = await launch({ proxy: opts.proxy });
|
|
138
137
|
cdp = await createCDP(browser.wsUrl);
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
let
|
|
140
|
+
let currentlyHeaded = (mode === 'headed');
|
|
141
|
+
let page = await createPage(cdp, !currentlyHeaded, { viewport: opts.viewport });
|
|
142
142
|
let refMap = new Map();
|
|
143
143
|
let botBlocked = false;
|
|
144
144
|
|
|
@@ -175,6 +175,20 @@ export async function connect(opts = {}) {
|
|
|
175
175
|
|
|
176
176
|
return {
|
|
177
177
|
async goto(url, timeout = 30000) {
|
|
178
|
+
// Switch back to headless if we fell back to headed previously
|
|
179
|
+
if (currentlyHeaded && mode === 'hybrid') {
|
|
180
|
+
await cdp.send('Target.closeTarget', { targetId: page.targetId });
|
|
181
|
+
cdp.close();
|
|
182
|
+
if (browser) { browser.process.kill(); browser = null; }
|
|
183
|
+
|
|
184
|
+
browser = await launch({ proxy: opts.proxy });
|
|
185
|
+
cdp = await createCDP(browser.wsUrl);
|
|
186
|
+
page = await createPage(cdp, true, { viewport: opts.viewport });
|
|
187
|
+
setupDialogHandler(page.session);
|
|
188
|
+
await suppressPermissions(cdp);
|
|
189
|
+
currentlyHeaded = false;
|
|
190
|
+
}
|
|
191
|
+
|
|
178
192
|
await navigate(page, url, timeout);
|
|
179
193
|
if (opts.consent !== false) {
|
|
180
194
|
await dismissConsent(page.session);
|
|
@@ -190,18 +204,22 @@ export async function connect(opts = {}) {
|
|
|
190
204
|
cdp.close();
|
|
191
205
|
if (browser) { browser.process.kill(); browser = null; }
|
|
192
206
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
try {
|
|
208
|
+
browser = await launch({ headed: true, proxy: opts.proxy });
|
|
209
|
+
cdp = await createCDP(browser.wsUrl);
|
|
210
|
+
page = await createPage(cdp, false, { viewport: opts.viewport });
|
|
211
|
+
setupDialogHandler(page.session);
|
|
212
|
+
await suppressPermissions(cdp);
|
|
213
|
+
await navigate(page, url, timeout);
|
|
214
|
+
if (opts.consent !== false) await dismissConsent(page.session);
|
|
215
|
+
|
|
216
|
+
// Re-check after headed fallback
|
|
217
|
+
const after = await ariaTree(page);
|
|
218
|
+
botBlocked = isChallengePage(after.tree, after.nodeCount);
|
|
219
|
+
currentlyHeaded = true;
|
|
220
|
+
} catch {
|
|
221
|
+
// Headed launch failed (no display?) — keep headless result, botBlocked stays true
|
|
222
|
+
}
|
|
205
223
|
}
|
|
206
224
|
},
|
|
207
225
|
|
|
@@ -375,7 +393,7 @@ export async function connect(opts = {}) {
|
|
|
375
393
|
cdp: page.session,
|
|
376
394
|
|
|
377
395
|
async createTab() {
|
|
378
|
-
const tab = await createPage(cdp,
|
|
396
|
+
const tab = await createPage(cdp, !currentlyHeaded, { viewport: opts.viewport });
|
|
379
397
|
await suppressPermissions(cdp);
|
|
380
398
|
let tabBotBlocked = false;
|
|
381
399
|
return {
|