barebrowse 0.5.3 → 0.5.4
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/.aurora/plans/active/harden-assess/design.md +68 -0
- package/.aurora/plans/active/harden-assess/plan.md +71 -0
- package/.aurora/plans/active/harden-assess/prd.md +38 -0
- package/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/barebrowse.context.md +5 -4
- package/mcp-server.js +93 -15
- package/package.json +1 -1
- package/src/cdp.js +7 -0
- package/src/index.js +20 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Design: Harden assess tool
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
### Current flow (broken)
|
|
6
|
+
```
|
|
7
|
+
assess(url) → connect({ mode: 'hybrid' }) ← NEW browser, no cookies
|
|
8
|
+
→ assessFn(page, url)
|
|
9
|
+
→ page.close() ← kills browser
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### New flow
|
|
13
|
+
```
|
|
14
|
+
assess(url) → getPage() ← reuse session browser
|
|
15
|
+
→ page.createTab() ← new tab in same browser
|
|
16
|
+
→ tab.injectCookies(url) ← cookie injection
|
|
17
|
+
→ assessFn(tab, url) ← assess uses tab
|
|
18
|
+
→ tab.close() ← close tab only
|
|
19
|
+
timeout guard wraps entire flow
|
|
20
|
+
retry wraps entire flow
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Key design decisions
|
|
24
|
+
|
|
25
|
+
### Why a new tab, not the session page?
|
|
26
|
+
wearehere's `assess()` calls:
|
|
27
|
+
- `session.send('Page.addScriptToEvaluateOnNewDocument', ...)` — injects fingerprint detection scripts
|
|
28
|
+
- `networkSession.on('Network.requestWillBeSent', ...)` — monitors all network traffic
|
|
29
|
+
|
|
30
|
+
These would pollute the session page. A separate tab has its own CDP session with isolated Page/Network domains.
|
|
31
|
+
|
|
32
|
+
### createTab() page-like interface
|
|
33
|
+
wearehere expects a page object with: `goto()`, `cdp` (raw session), `waitForNetworkIdle()`. createTab() returns exactly this interface:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
{
|
|
37
|
+
goto(url, timeout) // navigate tab
|
|
38
|
+
cdp // raw CDP session for this tab
|
|
39
|
+
waitForNetworkIdle(opts) // reuses existing waitForNetworkIdle()
|
|
40
|
+
injectCookies(url) // cookie injection for this tab
|
|
41
|
+
close() // close tab, NOT the browser
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Retry strategy
|
|
46
|
+
```
|
|
47
|
+
attempt 1: assess with 45s timeout
|
|
48
|
+
fail → wait 2s
|
|
49
|
+
attempt 2: assess with 45s timeout (if browser crashed, reset _page first)
|
|
50
|
+
fail → return { error: "assessment_failed", ... }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Timeout implementation
|
|
54
|
+
```javascript
|
|
55
|
+
const result = await Promise.race([
|
|
56
|
+
doAssess(page, url, opts),
|
|
57
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 45000))
|
|
58
|
+
]);
|
|
59
|
+
```
|
|
60
|
+
On timeout, the tab is closed in a finally block.
|
|
61
|
+
|
|
62
|
+
## Files changed
|
|
63
|
+
| File | Change |
|
|
64
|
+
|------|--------|
|
|
65
|
+
| `src/index.js` | Add `createTab()` and tab's `close()` to connect() return object |
|
|
66
|
+
| `mcp-server.js` | Rewrite assess case: use getPage().createTab(), add retry + timeout |
|
|
67
|
+
| `README.md` | Update assess description, add self-healing mention |
|
|
68
|
+
| `barebrowse.context.md` | Update assess section, document createTab() |
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Plan: Harden assess tool — session reuse + self-healing
|
|
2
|
+
|
|
3
|
+
## Plan ID
|
|
4
|
+
`harden-assess`
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
Make the assess tool reuse the MCP session's browser instance (cookies, headed fallback) instead of spawning throwaway browsers, add retry logic for transient failures, and add a timeout guard so no single assessment can hang forever.
|
|
8
|
+
|
|
9
|
+
## Problem Statement
|
|
10
|
+
The `assess` tool in `mcp-server.js` calls `connect({ mode: 'hybrid' })` directly — creating a fresh headless browser per call with no cookies, no session state, and no headed fallback. Every other MCP tool uses the `getPage()` singleton. This causes:
|
|
11
|
+
|
|
12
|
+
1. **No cookies** — assess browses as a stranger, getting blocked by consent walls and bot detection that cookies would bypass
|
|
13
|
+
2. **No headed fallback reuse** — if the singleton already fell back to headed mode, assess still starts fresh headless and hits the same blocks
|
|
14
|
+
3. **No retry** — any failure (browser crash, navigation timeout, CDP disconnect) kills the assessment with no recovery
|
|
15
|
+
4. **No timeout guard** — if `wearehere`'s `assess()` hangs (e.g. network idle never resolves), the MCP call blocks indefinitely
|
|
16
|
+
|
|
17
|
+
However, assess can't simply use the shared `_page` singleton directly because `wearehere` injects init scripts (`addScriptToEvaluateOnNewDocument`) and network listeners that would pollute the session page for subsequent calls. The solution is to create a **second page tab** within the same browser instance.
|
|
18
|
+
|
|
19
|
+
## Proposed Solution
|
|
20
|
+
1. **Session-aware page creation** — Instead of `connect()`, assess opens a new CDP tab within the existing browser. This shares the browser process (same cookies, same headed/headless state) but isolates assess's script injections to its own tab.
|
|
21
|
+
2. **Retry with backoff** — Wrap the assess call in a retry loop (max 2 attempts, 2s backoff). On browser crash, reconnect the singleton.
|
|
22
|
+
3. **Timeout guard** — Wrap each assess call in `Promise.race` with a 45s hard deadline. If exceeded, return an error result (not hang).
|
|
23
|
+
|
|
24
|
+
## Benefits
|
|
25
|
+
- Assess gets cookies and headed fallback for free — no separate browser instance
|
|
26
|
+
- Failed assessments auto-retry instead of dying
|
|
27
|
+
- Hanging assessments time out gracefully instead of blocking the MCP server forever
|
|
28
|
+
- Eliminates the 10+ second cold-start per assessment (browser launch)
|
|
29
|
+
|
|
30
|
+
## Scope
|
|
31
|
+
### In Scope
|
|
32
|
+
- Modify `mcp-server.js` assess handler to create tabs within existing browser
|
|
33
|
+
- Add `createTab()` / `closeTab()` helper to `src/index.js` connect() page handle
|
|
34
|
+
- Add retry wrapper in mcp-server.js
|
|
35
|
+
- Add timeout guard in mcp-server.js
|
|
36
|
+
- Update docs: README.md, barebrowse.context.md, CLAUDE.md
|
|
37
|
+
|
|
38
|
+
### Out of Scope
|
|
39
|
+
- Changing wearehere's internal logic
|
|
40
|
+
- Adding retry/self-healing to other MCP tools (future work)
|
|
41
|
+
- Batch/queue mode for multiple assessments
|
|
42
|
+
- Changing the assess tool's MCP interface (same inputs/outputs)
|
|
43
|
+
|
|
44
|
+
## Dependencies
|
|
45
|
+
- `wearehere` package (assess function signature unchanged)
|
|
46
|
+
- `src/index.js` connect() API (adding createTab/closeTab methods)
|
|
47
|
+
- `src/cdp.js` (Target.createTarget / closeTarget already available)
|
|
48
|
+
|
|
49
|
+
## Implementation Strategy
|
|
50
|
+
Phase 1: Add tab management to connect() page handle (createTab, closeTab)
|
|
51
|
+
Phase 2: Rewrite assess handler to use session tab + retry + timeout
|
|
52
|
+
Phase 3: Update documentation
|
|
53
|
+
|
|
54
|
+
## Risks and Mitigations
|
|
55
|
+
| Risk | Impact | Mitigation |
|
|
56
|
+
|------|--------|------------|
|
|
57
|
+
| Init script injection leaks across tabs | Pollutes session page | Each tab gets its own Page domain; addScriptToEvaluateOnNewDocument is per-target |
|
|
58
|
+
| Browser crash during assess kills session too | Session page lost | getPage() already handles reconnection lazily (set _page = null, next call recreates) |
|
|
59
|
+
| wearehere expects full page handle, not raw CDP session | API mismatch | createTab() returns a page-like object with goto, cdp, waitForNetworkIdle — same interface |
|
|
60
|
+
|
|
61
|
+
## Success Criteria
|
|
62
|
+
- [ ] `assess` reuses the session browser (no separate `connect()` call)
|
|
63
|
+
- [ ] `assess` inherits cookies from the session
|
|
64
|
+
- [ ] `assess` works when session is in headed mode (hybrid fallback already triggered)
|
|
65
|
+
- [ ] Failed assessment retries once before returning error
|
|
66
|
+
- [ ] Assessment hanging > 45s returns timeout error, doesn't block server
|
|
67
|
+
- [ ] All existing tests pass
|
|
68
|
+
- [ ] Documentation updated
|
|
69
|
+
|
|
70
|
+
## Open Questions
|
|
71
|
+
1. Should createTab() also inject cookies? — **Recommendation**: Yes, call `authenticate()` for the target URL before navigation, same as `goto` does.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# PRD: Harden assess tool
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
The `assess` MCP tool must reuse the session browser, retry on failure, and time out gracefully.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
|
|
8
|
+
### R1: Session-aware tab creation
|
|
9
|
+
The assess tool MUST create a new browser tab within the existing MCP session browser instead of spawning a separate browser via `connect()`. The tab MUST:
|
|
10
|
+
- Share the same browser process (inheriting headless/headed state)
|
|
11
|
+
- Have access to the browser's cookie jar
|
|
12
|
+
- Isolate its CDP domains (Page, Network, DOM) from the session page
|
|
13
|
+
- Be closed after each assessment completes or fails
|
|
14
|
+
|
|
15
|
+
### R2: Cookie injection
|
|
16
|
+
Before navigating, the assess tab MUST inject cookies from the user's browser for the target URL, using the same `authenticate()` mechanism as `goto`.
|
|
17
|
+
|
|
18
|
+
### R3: Retry on failure
|
|
19
|
+
If an assessment fails (navigation timeout, CDP error, browser crash), the tool MUST:
|
|
20
|
+
- Retry once after a 2-second delay
|
|
21
|
+
- If the browser crashed, reset the session singleton (`_page = null`) so getPage() reconnects
|
|
22
|
+
- If retry also fails, return a structured error result (not throw)
|
|
23
|
+
|
|
24
|
+
### R4: Timeout guard
|
|
25
|
+
Each assessment MUST have a hard timeout of 45 seconds. If exceeded:
|
|
26
|
+
- The tab is force-closed
|
|
27
|
+
- A structured error result is returned: `{ site, url, error: "timeout", scanned_at }`
|
|
28
|
+
- The session page is NOT affected
|
|
29
|
+
|
|
30
|
+
### R5: Backwards compatibility
|
|
31
|
+
- The assess tool's MCP interface (inputs/outputs) MUST NOT change
|
|
32
|
+
- Successful assessments return the same JSON format as before
|
|
33
|
+
- The tool appears/disappears based on wearehere availability (unchanged)
|
|
34
|
+
|
|
35
|
+
## Non-functional
|
|
36
|
+
- No new dependencies
|
|
37
|
+
- No changes to wearehere package
|
|
38
|
+
- createTab()/closeTab() exposed on connect() page handle for library users too
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.4
|
|
4
|
+
|
|
5
|
+
Assess tool hardened: session reuse, concurrency, self-healing.
|
|
6
|
+
|
|
7
|
+
### Assess handler (`mcp-server.js`)
|
|
8
|
+
- Session reuse: assess now opens tabs in the existing session browser via `createTab()` instead of spawning a new browser per scan
|
|
9
|
+
- Concurrency: semaphore limits to 3 concurrent assess tabs, queues the rest
|
|
10
|
+
- 30s hard timeout per scan with force tab close on expiry
|
|
11
|
+
- Auto-retry once on failure with 2s backoff; resets session on CDP crash
|
|
12
|
+
- Removed debug logging added during development
|
|
13
|
+
|
|
14
|
+
### CDP resilience (`src/cdp.js`)
|
|
15
|
+
- `ws.onclose` handler rejects all pending CDP promises — prevents zombie promises on browser crash
|
|
16
|
+
|
|
17
|
+
### Library API (`src/index.js`)
|
|
18
|
+
- `createTab()` added to `connect()` return object — creates new tab in same browser session
|
|
19
|
+
- Returns `{ goto, injectCookies, waitForNetworkIdle, cdp, close }`, tab close doesn't affect main page
|
|
20
|
+
|
|
21
|
+
### Message loop (`mcp-server.js`)
|
|
22
|
+
- `getPage()` is concurrency-safe with promise dedup (prevents duplicate browser launches)
|
|
23
|
+
- Message loop changed from sequential `await` to concurrent `.then()` fire-and-forget — multiple tool calls no longer block each other
|
|
24
|
+
|
|
25
|
+
### Tests
|
|
26
|
+
- 69/69 passing
|
|
27
|
+
- Verified: 10 concurrent assess calls through MCP client, all succeed (3-at-a-time semaphore confirmed)
|
|
28
|
+
|
|
3
29
|
## 0.5.3
|
|
4
30
|
|
|
5
31
|
Improved bot detection for hybrid mode fallback.
|
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ Everything the agent can do through barebrowse:
|
|
|
139
139
|
| **Upload** | Set files on a file input element |
|
|
140
140
|
| **Screenshot** | Page capture as base64 PNG/JPEG/WebP |
|
|
141
141
|
| **PDF** | Export page as PDF |
|
|
142
|
-
| **Assess** | Privacy scan: score (0-100), risk level, 10-category breakdown. Requires `npm install wearehere`. |
|
|
142
|
+
| **Assess** | Privacy scan: score (0-100), risk level, 10-category breakdown. Reuses session browser via tabs (max 3 concurrent), auto-retries on CDP crash, 30s timeout per scan. Requires `npm install wearehere`. |
|
|
143
143
|
| **Tabs** | List open tabs, switch between them |
|
|
144
144
|
| **Wait for content** | Poll for text or CSS selector to appear on page |
|
|
145
145
|
| **Wait for navigation** | SPA-aware: works for full page loads and pushState |
|
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.5.
|
|
4
|
+
> v0.5.4 | Node.js >= 22 | 0 required deps | MIT
|
|
5
5
|
|
|
6
6
|
## What this is
|
|
7
7
|
|
|
@@ -78,6 +78,7 @@ const snapshot = await browse('https://example.com', {
|
|
|
78
78
|
| `injectCookies(url, opts?)` | url: string, { browser?: string } | void | Extract cookies from user's browser and inject via CDP |
|
|
79
79
|
| `dialogLog` | -- | Array<{type, message, timestamp}> | Auto-dismissed JS dialog history |
|
|
80
80
|
| `cdp` | -- | object | Raw CDP session for escape hatch: `page.cdp.send(method, params)` |
|
|
81
|
+
| `createTab()` | -- | tab handle | New tab in same browser. Returns `{ goto, injectCookies, waitForNetworkIdle, cdp, close }`. Tab close doesn't affect session. |
|
|
81
82
|
| `close()` | -- | void | Close page, disconnect CDP, kill browser (if headless) |
|
|
82
83
|
|
|
83
84
|
**connect() options** (in addition to mode/port/consent):
|
|
@@ -241,7 +242,7 @@ Action tools return `'ok'` -- the agent calls `snapshot` explicitly to observe.
|
|
|
241
242
|
|
|
242
243
|
Session runs in hybrid mode (headless with automatic headed fallback on bot detection). `goto` injects cookies from the user's browser before navigation for authenticated access.
|
|
243
244
|
|
|
244
|
-
Session tools share a singleton page, lazy-created on first use.
|
|
245
|
+
Session tools share a singleton page, lazy-created on first use. Assess runs in isolated tabs within the session (max 3 concurrent, with retry and CDP crash recovery).
|
|
245
246
|
|
|
246
247
|
## Architecture
|
|
247
248
|
|
|
@@ -271,7 +272,7 @@ URL -> chromium.js (find/launch browser, permission flags)
|
|
|
271
272
|
| `src/consent.js` | 200 | Auto-dismiss cookie consent dialogs across languages |
|
|
272
273
|
| `src/stealth.js` | ~40 | Navigator patches for headless anti-detection |
|
|
273
274
|
| `src/bareagent.js` | ~250 | Tool adapter for bareagent Loop |
|
|
274
|
-
| `mcp-server.js` | ~
|
|
275
|
+
| `mcp-server.js` | ~340 | MCP server (JSON-RPC over stdio, assess session reuse + concurrency) |
|
|
275
276
|
|
|
276
277
|
## Privacy assessment (optional)
|
|
277
278
|
|
|
@@ -281,7 +282,7 @@ Install `wearehere` to add an `assess` tool to both the MCP server and bareagent
|
|
|
281
282
|
npm install wearehere
|
|
282
283
|
```
|
|
283
284
|
|
|
284
|
-
The `assess` tool
|
|
285
|
+
The `assess` tool opens a new tab in the session browser via `createTab()` (reusing cookies and headed fallback), scans for 10 privacy categories (cookies, trackers, fingerprinting, dark patterns, data brokers, form surveillance, link tracking, toxic terms, stored data, network traffic), and returns a compact JSON assessment. Max 3 concurrent scans (queued beyond that), 30s hard timeout per scan, auto-retry once on failure with 2s backoff (session reset on CDP crash):
|
|
285
286
|
|
|
286
287
|
```json
|
|
287
288
|
{
|
package/mcp-server.js
CHANGED
|
@@ -20,6 +20,11 @@ try {
|
|
|
20
20
|
} catch {}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
function isCdpDead(err) {
|
|
24
|
+
const m = err.message || '';
|
|
25
|
+
return m.includes('WebSocket') || m.includes('Target closed') || m.includes('Session closed') || m.includes('CDP');
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
const MAX_CHARS_DEFAULT = 30000;
|
|
24
29
|
const OUTPUT_DIR = join(process.cwd(), '.barebrowse');
|
|
25
30
|
|
|
@@ -32,10 +37,42 @@ function saveSnapshot(text) {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
let _page = null;
|
|
40
|
+
let _pageConnecting = null;
|
|
35
41
|
|
|
36
42
|
async function getPage() {
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
43
|
+
if (_page) return _page;
|
|
44
|
+
if (_pageConnecting) return _pageConnecting;
|
|
45
|
+
_pageConnecting = connect({ mode: 'hybrid' });
|
|
46
|
+
try {
|
|
47
|
+
_page = await _pageConnecting;
|
|
48
|
+
return _page;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
_page = null;
|
|
51
|
+
throw err;
|
|
52
|
+
} finally {
|
|
53
|
+
_pageConnecting = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Concurrency limiter for assess — max 3 tabs at once
|
|
58
|
+
const ASSESS_MAX = 3;
|
|
59
|
+
let _assessRunning = 0;
|
|
60
|
+
const _assessQueue = [];
|
|
61
|
+
|
|
62
|
+
function acquireAssessSlot() {
|
|
63
|
+
if (_assessRunning < ASSESS_MAX) {
|
|
64
|
+
_assessRunning++;
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}
|
|
67
|
+
return new Promise((resolve) => _assessQueue.push(resolve));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function releaseAssessSlot() {
|
|
71
|
+
if (_assessQueue.length > 0) {
|
|
72
|
+
_assessQueue.shift()();
|
|
73
|
+
} else {
|
|
74
|
+
_assessRunning--;
|
|
75
|
+
}
|
|
39
76
|
}
|
|
40
77
|
|
|
41
78
|
const TOOLS = [
|
|
@@ -255,15 +292,47 @@ async function handleToolCall(name, args) {
|
|
|
255
292
|
}
|
|
256
293
|
case 'assess': {
|
|
257
294
|
if (!assessFn) throw new Error('wearehere is not installed. Run: npm install wearehere');
|
|
258
|
-
|
|
295
|
+
await acquireAssessSlot();
|
|
259
296
|
try {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
297
|
+
const runAssess = async () => {
|
|
298
|
+
const page = await getPage();
|
|
299
|
+
const tab = await page.createTab();
|
|
300
|
+
let timer;
|
|
301
|
+
try {
|
|
302
|
+
const result = await Promise.race([
|
|
303
|
+
(async () => {
|
|
304
|
+
await tab.injectCookies(args.url).catch(() => {});
|
|
305
|
+
return await assessFn(tab, args.url, { timeout: args.timeout, settle: args.settle });
|
|
306
|
+
})(),
|
|
307
|
+
new Promise((_, reject) => {
|
|
308
|
+
timer = setTimeout(() => {
|
|
309
|
+
tab.close().catch(() => {});
|
|
310
|
+
reject(new Error('assess timeout'));
|
|
311
|
+
}, 30000);
|
|
312
|
+
}),
|
|
313
|
+
]);
|
|
314
|
+
clearTimeout(timer);
|
|
315
|
+
return JSON.stringify(result, null, 2);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
await tab.close().catch(() => {});
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
try {
|
|
323
|
+
return await runAssess();
|
|
324
|
+
} catch (err) {
|
|
325
|
+
if (isCdpDead(err)) _page = null;
|
|
326
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
327
|
+
try {
|
|
328
|
+
return await runAssess();
|
|
329
|
+
} catch (retryErr) {
|
|
330
|
+
if (isCdpDead(retryErr)) _page = null;
|
|
331
|
+
throw retryErr;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
265
334
|
} finally {
|
|
266
|
-
|
|
335
|
+
releaseAssessSlot();
|
|
267
336
|
}
|
|
268
337
|
}
|
|
269
338
|
default:
|
|
@@ -286,7 +355,7 @@ async function handleMessage(msg) {
|
|
|
286
355
|
return jsonrpcResponse(id, {
|
|
287
356
|
protocolVersion: '2024-11-05',
|
|
288
357
|
capabilities: { tools: {} },
|
|
289
|
-
serverInfo: { name: 'barebrowse', version: '0.5.
|
|
358
|
+
serverInfo: { name: 'barebrowse', version: '0.5.4' },
|
|
290
359
|
});
|
|
291
360
|
}
|
|
292
361
|
|
|
@@ -321,8 +390,9 @@ async function handleMessage(msg) {
|
|
|
321
390
|
let buffer = '';
|
|
322
391
|
|
|
323
392
|
process.stdin.setEncoding('utf8');
|
|
324
|
-
process.stdin.on('data',
|
|
393
|
+
process.stdin.on('data', (chunk) => {
|
|
325
394
|
buffer += chunk;
|
|
395
|
+
|
|
326
396
|
let newlineIdx;
|
|
327
397
|
while ((newlineIdx = buffer.indexOf('\n')) !== -1) {
|
|
328
398
|
const line = buffer.slice(0, newlineIdx).trim();
|
|
@@ -331,11 +401,19 @@ process.stdin.on('data', async (chunk) => {
|
|
|
331
401
|
|
|
332
402
|
try {
|
|
333
403
|
const msg = JSON.parse(line);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
404
|
+
|
|
405
|
+
handleMessage(msg).then((response) => {
|
|
406
|
+
if (response) {
|
|
407
|
+
|
|
408
|
+
process.stdout.write(response + '\n');
|
|
409
|
+
|
|
410
|
+
}
|
|
411
|
+
}).catch((err) => {
|
|
412
|
+
|
|
413
|
+
process.stdout.write(jsonrpcError(msg.id, -32700, `Error: ${err.message}`) + '\n');
|
|
414
|
+
});
|
|
338
415
|
} catch (err) {
|
|
416
|
+
|
|
339
417
|
process.stdout.write(jsonrpcError(null, -32700, `Parse error: ${err.message}`) + '\n');
|
|
340
418
|
}
|
|
341
419
|
}
|
package/package.json
CHANGED
package/src/cdp.js
CHANGED
|
@@ -71,6 +71,13 @@ export async function createCDP(wsUrl) {
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
ws.onclose = () => {
|
|
75
|
+
for (const [id, handler] of pending) {
|
|
76
|
+
handler.reject(new Error('CDP WebSocket closed'));
|
|
77
|
+
pending.delete(id);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
74
81
|
const client = {
|
|
75
82
|
/**
|
|
76
83
|
* Send a CDP command and wait for the response.
|
package/src/index.js
CHANGED
|
@@ -365,6 +365,26 @@ export async function connect(opts = {}) {
|
|
|
365
365
|
/** Raw CDP session for escape hatch */
|
|
366
366
|
cdp: page.session,
|
|
367
367
|
|
|
368
|
+
async createTab() {
|
|
369
|
+
const tab = await createPage(cdp, mode !== 'headed', { viewport: opts.viewport });
|
|
370
|
+
await suppressPermissions(cdp);
|
|
371
|
+
return {
|
|
372
|
+
async goto(url, timeout = 30000) {
|
|
373
|
+
await navigate(tab, url, timeout);
|
|
374
|
+
},
|
|
375
|
+
async injectCookies(url, cookieOpts) {
|
|
376
|
+
await authenticate(tab.session, url, { browser: cookieOpts?.browser });
|
|
377
|
+
},
|
|
378
|
+
waitForNetworkIdle(idleOpts = {}) {
|
|
379
|
+
return waitForNetworkIdle(tab.session, idleOpts);
|
|
380
|
+
},
|
|
381
|
+
cdp: tab.session,
|
|
382
|
+
async close() {
|
|
383
|
+
await cdp.send('Target.closeTarget', { targetId: tab.targetId });
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
|
|
368
388
|
async close() {
|
|
369
389
|
await cdp.send('Target.closeTarget', { targetId: page.targetId });
|
|
370
390
|
cdp.close();
|