barebrowse 0.14.0 → 0.15.0
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 +47 -0
- package/README.md +16 -1
- package/barebrowse.context.md +3 -0
- package/cli.js +10 -2
- package/mcp-server.js +14 -2
- package/package.json +1 -1
- package/src/auth.js +27 -13
- package/src/ax-snapshot.js +287 -0
- package/src/bidi.js +143 -0
- package/src/daemon.js +92 -51
- package/src/firefox-page.js +416 -0
- package/src/firefox.js +203 -0
- package/src/index.js +75 -6
- package/src/readable.js +32 -20
- package/types/auth.d.ts +18 -1
- package/types/ax-snapshot.d.ts +35 -0
- package/types/bidi.d.ts +10 -0
- package/types/daemon.d.ts +12 -0
- package/types/firefox-page.d.ts +19 -0
- package/types/firefox.d.ts +41 -0
- package/types/index.d.ts +13 -2
- package/types/readable.d.ts +8 -0
package/src/index.js
CHANGED
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { launch, attach, cleanupBrowser } from './chromium.js';
|
|
12
|
+
import { launchFirefox, cleanupFirefox } from './firefox.js';
|
|
12
13
|
import { createCDP } from './cdp.js';
|
|
14
|
+
import { createBiDi } from './bidi.js';
|
|
15
|
+
import { createFirefoxPage } from './firefox-page.js';
|
|
13
16
|
import { formatTree } from './aria.js';
|
|
14
17
|
import { authenticate } from './auth.js';
|
|
15
18
|
import { prune as pruneTree } from './prune.js';
|
|
@@ -31,6 +34,8 @@ import { chmodSync } from 'node:fs';
|
|
|
31
34
|
* @param {object} [opts]
|
|
32
35
|
* @param {'headless'|'headed'|'hybrid'} [opts.mode='headless'] - Browser mode
|
|
33
36
|
* @param {boolean} [opts.cookies=true] - Inject user's cookies (Phase 2)
|
|
37
|
+
* @param {boolean} [opts.incognito=false] - Clean, unauthenticated session:
|
|
38
|
+
* skip cookie/auth injection entirely (equivalent to cookies:false here).
|
|
34
39
|
* @param {boolean} [opts.prune=true] - Apply ARIA pruning (Phase 2)
|
|
35
40
|
* @param {number} [opts.timeout=30000] - Navigation timeout in ms
|
|
36
41
|
* @param {boolean} [opts.blockAds=true] - Block ~120 common ad/tracker
|
|
@@ -54,6 +59,8 @@ import { chmodSync } from 'node:fs';
|
|
|
54
59
|
export async function browse(url, opts = {}) {
|
|
55
60
|
const mode = opts.mode || 'headless';
|
|
56
61
|
const timeout = opts.timeout || 30000;
|
|
62
|
+
// Skip all auth injection when incognito (or the legacy cookies:false).
|
|
63
|
+
const noAuth = !!opts.incognito || opts.cookies === false;
|
|
57
64
|
|
|
58
65
|
// Reject local-resource schemes (and optionally private hosts) before we
|
|
59
66
|
// spend a browser launch on a URL we won't navigate to.
|
|
@@ -84,7 +91,7 @@ export async function browse(url, opts = {}) {
|
|
|
84
91
|
await suppressPermissions(cdp);
|
|
85
92
|
|
|
86
93
|
// Step 3: Cookie injection — extract from user's browser, inject via CDP
|
|
87
|
-
if (
|
|
94
|
+
if (!noAuth) {
|
|
88
95
|
try {
|
|
89
96
|
await authenticate(page.session, url, { browser: opts.browser });
|
|
90
97
|
} catch {
|
|
@@ -114,7 +121,7 @@ export async function browse(url, opts = {}) {
|
|
|
114
121
|
cdp = await createCDP(browser.wsUrl);
|
|
115
122
|
page = await createPage(cdp, false, pageOpts);
|
|
116
123
|
await suppressPermissions(cdp);
|
|
117
|
-
if (
|
|
124
|
+
if (!noAuth) {
|
|
118
125
|
try { await authenticate(page.session, url, { browser: opts.browser }); } catch {}
|
|
119
126
|
}
|
|
120
127
|
await navigate(page, url, timeout);
|
|
@@ -188,12 +195,32 @@ export async function browse(url, opts = {}) {
|
|
|
188
195
|
* @param {boolean} [opts.consent=true] - Auto-dismiss cookie consent dialogs.
|
|
189
196
|
* @param {string} [opts.storageState] - Path to a storage-state JSON file
|
|
190
197
|
* (cookies + localStorage) to load before navigation.
|
|
198
|
+
* @param {boolean} [opts.incognito=false] - Clean, unauthenticated session:
|
|
199
|
+
* skip storageState loading and make injectCookies() a no-op, so no auth
|
|
200
|
+
* ever enters the session even if a caller injects unconditionally.
|
|
191
201
|
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Pruning mode.
|
|
202
|
+
* @param {'chromium'|'firefox'} [opts.engine='chromium'] - Browser engine.
|
|
203
|
+
* 'firefox' drives over WebDriver BiDi (a separate transport / page object);
|
|
204
|
+
* 'chromium' (default) uses CDP.
|
|
192
205
|
* @returns {Promise<object>} Page handle with goto, snapshot, close
|
|
193
206
|
*/
|
|
194
207
|
export async function connect(opts = {}) {
|
|
208
|
+
// Firefox is driven over WebDriver BiDi (CDP is deprecated there) — a
|
|
209
|
+
// separate transport with its own page object. It reuses prune.js/aria.js/
|
|
210
|
+
// readable.js but none of the CDP page machinery below, so branch early.
|
|
211
|
+
if (opts.engine === 'firefox') {
|
|
212
|
+
return connectFirefox(opts);
|
|
213
|
+
}
|
|
214
|
+
|
|
195
215
|
const mode = opts.mode || 'headless';
|
|
196
216
|
const attachMode = !!opts.port;
|
|
217
|
+
// Incognito: run a clean, unauthenticated session. Skips storageState loading
|
|
218
|
+
// and neuters injectCookies() so no auth ever enters the session — even when
|
|
219
|
+
// a caller (MCP goto, daemon) calls injectCookies() unconditionally. The
|
|
220
|
+
// temp profile is already throwaway; this gates the OTHER auth source: the
|
|
221
|
+
// user's real browser cookies. Not Chrome's --incognito flag (redundant with
|
|
222
|
+
// the temp profile and would break the cookie-injection path when off).
|
|
223
|
+
const incognito = !!opts.incognito;
|
|
197
224
|
let browser = null;
|
|
198
225
|
let cdp;
|
|
199
226
|
// Forward caller-supplied launch knobs into every launch() below,
|
|
@@ -243,8 +270,9 @@ export async function connect(opts = {}) {
|
|
|
243
270
|
await suppressPermissions(cdp);
|
|
244
271
|
}
|
|
245
272
|
|
|
246
|
-
// Load storage state (cookies + localStorage) from file
|
|
247
|
-
|
|
273
|
+
// Load storage state (cookies + localStorage) from file.
|
|
274
|
+
// Skipped under incognito — storageState is an auth source too.
|
|
275
|
+
if (opts.storageState && !incognito) {
|
|
248
276
|
try {
|
|
249
277
|
const { readFileSync } = await import('node:fs');
|
|
250
278
|
const state = JSON.parse(readFileSync(opts.storageState, 'utf8'));
|
|
@@ -439,7 +467,10 @@ export async function connect(opts = {}) {
|
|
|
439
467
|
},
|
|
440
468
|
|
|
441
469
|
async injectCookies(url, cookieOpts) {
|
|
442
|
-
|
|
470
|
+
// No-op under incognito: callers (MCP goto, daemon) inject unconditionally,
|
|
471
|
+
// so the gate has to live here, not just at the call site.
|
|
472
|
+
if (incognito) return 0;
|
|
473
|
+
return authenticate(page.session, url, { browser: cookieOpts?.browser });
|
|
443
474
|
},
|
|
444
475
|
|
|
445
476
|
async snapshot(pruneOpts) {
|
|
@@ -655,7 +686,8 @@ export async function connect(opts = {}) {
|
|
|
655
686
|
},
|
|
656
687
|
get botBlocked() { return tabBotBlocked; },
|
|
657
688
|
async injectCookies(url, cookieOpts) {
|
|
658
|
-
|
|
689
|
+
if (incognito) return 0;
|
|
690
|
+
return authenticate(tab.session, url, { browser: cookieOpts?.browser });
|
|
659
691
|
},
|
|
660
692
|
waitForNetworkIdle(idleOpts = {}) {
|
|
661
693
|
return waitForNetworkIdle(tab.session, idleOpts);
|
|
@@ -708,6 +740,43 @@ async function suppressPermissions(cdp) {
|
|
|
708
740
|
}
|
|
709
741
|
}
|
|
710
742
|
|
|
743
|
+
/**
|
|
744
|
+
* Firefox path for connect(): launch Firefox, open a BiDi session, and return
|
|
745
|
+
* a BiDi-backed page object. Separate from the CDP path because the transport,
|
|
746
|
+
* ref model, and AX-tree source all differ (see firefox-page.js). close() is
|
|
747
|
+
* wrapped to also reap the Firefox process + temp profile.
|
|
748
|
+
*
|
|
749
|
+
* Chromium-only options NOT applied on this path: `consent` (auto-dismiss),
|
|
750
|
+
* `blockAds`/`blockUrls`, stealth patches, and `hybrid` mode. `saveState`,
|
|
751
|
+
* `waitForNavigation`, `waitForNetworkIdle`, and download/dialog capture are
|
|
752
|
+
* CDP-only too (the page object stubs them — see firefox-page.js). The
|
|
753
|
+
* navigation guard (`allowLocalUrls`/`blockPrivateNetwork`), `uploadDir`
|
|
754
|
+
* sandbox, `incognito`, `proxy`, `viewport`, and `pruneMode` DO apply.
|
|
755
|
+
* @param {object} opts - connect() options ({ mode, proxy, binary, viewport, pruneMode, urlGuard, uploadDir, incognito })
|
|
756
|
+
* @returns {Promise<object>} Firefox page object
|
|
757
|
+
*/
|
|
758
|
+
async function connectFirefox(opts) {
|
|
759
|
+
const browser = await launchFirefox({
|
|
760
|
+
headed: opts.mode === 'headed',
|
|
761
|
+
proxy: opts.proxy,
|
|
762
|
+
binary: opts.binary,
|
|
763
|
+
viewport: opts.viewport,
|
|
764
|
+
});
|
|
765
|
+
const bidi = await createBiDi(browser.wsUrl);
|
|
766
|
+
const page = await createFirefoxPage(bidi, {
|
|
767
|
+
pruneMode: opts.pruneMode,
|
|
768
|
+
urlGuard: { allowLocalUrls: opts.allowLocalUrls, blockPrivateNetwork: opts.blockPrivateNetwork },
|
|
769
|
+
uploadDir: opts.uploadDir || null,
|
|
770
|
+
incognito: !!opts.incognito,
|
|
771
|
+
});
|
|
772
|
+
const closePage = page.close.bind(page);
|
|
773
|
+
page.close = async () => {
|
|
774
|
+
await closePage();
|
|
775
|
+
await cleanupFirefox(browser);
|
|
776
|
+
};
|
|
777
|
+
return page;
|
|
778
|
+
}
|
|
779
|
+
|
|
711
780
|
/**
|
|
712
781
|
* Create a new page target and return a session-scoped handle.
|
|
713
782
|
* @param {object} cdp - CDP client
|
package/src/readable.js
CHANGED
|
@@ -72,21 +72,21 @@ export function formatReadable(r) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* { ok: true, title, byline, text, length,
|
|
80
|
-
* confidence: 'high'|'low', readerable, hint? } — extracted article
|
|
75
|
+
* The in-page extraction expression (Readability injected + run). Exported so
|
|
76
|
+
* non-CDP transports (BiDi/Firefox) can evaluate the same source and feed the
|
|
77
|
+
* raw result through finalizeReadable() — keeping every engine's output
|
|
78
|
+
* identical.
|
|
81
79
|
*/
|
|
82
|
-
export
|
|
83
|
-
const { result } = await session.send('Runtime.evaluate', {
|
|
84
|
-
expression: EXTRACT_EXPRESSION,
|
|
85
|
-
returnByValue: true,
|
|
86
|
-
awaitPromise: true,
|
|
87
|
-
});
|
|
88
|
-
const r = result.value || {};
|
|
80
|
+
export { EXTRACT_EXPRESSION };
|
|
89
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Turn the raw in-page extraction result into the public readable() shape,
|
|
84
|
+
* applying the advisory confidence rule. Transport-agnostic.
|
|
85
|
+
* @param {object} r - raw result from EXTRACT_EXPRESSION
|
|
86
|
+
* @returns {object} public readable() result
|
|
87
|
+
*/
|
|
88
|
+
export function finalizeReadable(r) {
|
|
89
|
+
r = r || {};
|
|
90
90
|
if (!r.ok) {
|
|
91
91
|
return {
|
|
92
92
|
ok: false,
|
|
@@ -95,22 +95,34 @@ export async function readable(session) {
|
|
|
95
95
|
: 'no article content found on this page; use snapshot() instead',
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
-
|
|
99
98
|
// Advisory confidence: high only when the reader-view heuristic agrees AND
|
|
100
99
|
// there is a substantial amount of text. Low is not an error — the text is
|
|
101
100
|
// still returned; it just means "verify, or prefer snapshot()".
|
|
102
101
|
const confidence = r.readerable && r.length >= MIN_ARTICLE_CHARS ? 'high' : 'low';
|
|
103
102
|
const out = {
|
|
104
103
|
ok: true,
|
|
105
|
-
title: r.title,
|
|
106
|
-
|
|
107
|
-
text: r.text,
|
|
108
|
-
length: r.length,
|
|
109
|
-
readerable: r.readerable,
|
|
110
|
-
confidence,
|
|
104
|
+
title: r.title, byline: r.byline, text: r.text, length: r.length,
|
|
105
|
+
readerable: r.readerable, confidence,
|
|
111
106
|
};
|
|
112
107
|
if (confidence === 'low') {
|
|
113
108
|
out.hint = 'low article confidence — this may not be an article; consider snapshot()';
|
|
114
109
|
}
|
|
115
110
|
return out;
|
|
116
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extract the main article from the current page.
|
|
115
|
+
* @param {object} session - CDP session-scoped handle (.send()).
|
|
116
|
+
* @returns {Promise<object>} One of:
|
|
117
|
+
* { ok: false, hint } — no article content found
|
|
118
|
+
* { ok: true, title, byline, text, length,
|
|
119
|
+
* confidence: 'high'|'low', readerable, hint? } — extracted article
|
|
120
|
+
*/
|
|
121
|
+
export async function readable(session) {
|
|
122
|
+
const { result } = await session.send('Runtime.evaluate', {
|
|
123
|
+
expression: EXTRACT_EXPRESSION,
|
|
124
|
+
returnByValue: true,
|
|
125
|
+
awaitPromise: true,
|
|
126
|
+
});
|
|
127
|
+
return finalizeReadable(result.value || {});
|
|
128
|
+
}
|
package/types/auth.d.ts
CHANGED
|
@@ -25,9 +25,26 @@ export function injectCookies(session: object, cookies: Array<object>): Promise<
|
|
|
25
25
|
* @returns {boolean}
|
|
26
26
|
*/
|
|
27
27
|
export function cookieDomainMatch(host: string, cookieDomain: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Select the user's cookies that are in-scope for `url`. Shared by BOTH engines
|
|
30
|
+
* (CDP `authenticate` and Firefox/BiDi `injectCookies`) so they can't drift on
|
|
31
|
+
* scoping — a divergence here re-opens loading the entire cookie jar into an
|
|
32
|
+
* agent session. Returns the filtered cookie array; never injects.
|
|
33
|
+
*
|
|
34
|
+
* Coarse SQL pre-filter strips to a registrable-ish domain so the LIKE query
|
|
35
|
+
* returns a superset (incl. parent-domain cookies). slice(-2) is a cheap
|
|
36
|
+
* heuristic — it over-selects for multi-part eTLDs (co.uk) and as a substring
|
|
37
|
+
* match, so the precise RFC-6265 domain-match is what actually decides which
|
|
38
|
+
* cookies pass. Without it, browsing apple.com would match apple.com.evil.org
|
|
39
|
+
* and every *.co.uk site (verified).
|
|
40
|
+
* @param {string} url - URL whose host the cookies must match
|
|
41
|
+
* @param {object} [opts] - passed to extractCookies (e.g. { browser })
|
|
42
|
+
* @returns {Array<object>} cookies whose domain matches the URL host
|
|
43
|
+
*/
|
|
44
|
+
export function scopedCookiesForUrl(url: string, opts?: object): Array<object>;
|
|
28
45
|
/**
|
|
29
46
|
* Extract cookies for a URL and inject them into a CDP session.
|
|
30
|
-
* Convenience function combining
|
|
47
|
+
* Convenience function combining scopedCookiesForUrl + injectCookies.
|
|
31
48
|
* @param {object} session - CDP session handle
|
|
32
49
|
* @param {string} url - URL to extract cookies for
|
|
33
50
|
* @param {object} [opts] - Options passed to extractCookies
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the script.evaluate expression that reconstructs the AX tree in a
|
|
3
|
+
* context, assigning refs from `base`.
|
|
4
|
+
* @param {number} base - Starting ref offset (exclusive; first ref is base+1)
|
|
5
|
+
* @returns {string} JS expression returning JSON { tree, count }
|
|
6
|
+
*/
|
|
7
|
+
export function axSnapshotExpression(base: number): string;
|
|
8
|
+
/**
|
|
9
|
+
* ax-snapshot.js — Reconstruct a CDP-shaped accessibility tree in-page.
|
|
10
|
+
*
|
|
11
|
+
* CDP hands you Accessibility.getFullAXTree; BiDi has no equivalent, so on
|
|
12
|
+
* Firefox we rebuild an equivalent tree inside the page via script.evaluate.
|
|
13
|
+
* The output tree uses the SAME node shape and role vocabulary as
|
|
14
|
+
* buildTree()'s CDP output, so prune.js and aria.js consume it unchanged:
|
|
15
|
+
*
|
|
16
|
+
* { nodeId, role, name, properties, children, ignored }
|
|
17
|
+
*
|
|
18
|
+
* where `role` is CDP/ARIA vocabulary ('RootWebArea', 'StaticText', 'heading',
|
|
19
|
+
* 'link', 'button', 'img', 'paragraph', 'list', 'navigation', …), NOT tag
|
|
20
|
+
* names. The three things getFullAXTree gave us for free and we reimplement:
|
|
21
|
+
* 1. implicit ARIA role mapping (HTML element → role)
|
|
22
|
+
* 2. accessible-name computation (aria-labelledby → aria-label → native
|
|
23
|
+
* label/alt/legend/title → text content) — the POC proved textContent
|
|
24
|
+
* alone is NOT enough (img alt, <label>, aria-labelledby all missed).
|
|
25
|
+
* 3. hidden-subtree filtering (aria-hidden, display:none, visibility:hidden,
|
|
26
|
+
* the hidden attribute).
|
|
27
|
+
*
|
|
28
|
+
* Each kept element is tagged with a data-bb-ref attribute carrying its ref,
|
|
29
|
+
* so firefox-page.js (resolveRef) can resolve a ref back to its element via
|
|
30
|
+
* querySelector.
|
|
31
|
+
* Refs are assigned from a caller-supplied `base` so they stay globally unique
|
|
32
|
+
* across browsing contexts (iframes), matching CDP's flat integer refs.
|
|
33
|
+
*/
|
|
34
|
+
/** Attribute used to tag elements for ref → element resolution. */
|
|
35
|
+
export const REF_ATTR: "data-bb-ref";
|
package/types/bidi.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a BiDi client connected to the given /session WebSocket URL and open
|
|
3
|
+
* a session. Firefox prints its BiDi endpoint to stderr as
|
|
4
|
+
* "WebDriver BiDi listening on ws://HOST:PORT"; the direct-connection socket
|
|
5
|
+
* (no WebDriver-classic handshake) lives at that URL + "/session".
|
|
6
|
+
*
|
|
7
|
+
* @param {string} wsUrl - BiDi session WebSocket URL (ws://HOST:PORT/session)
|
|
8
|
+
* @returns {Promise<object>} BiDi client ({ send, evaluate, on, once, subscribe, sessionId, close })
|
|
9
|
+
*/
|
|
10
|
+
export function createBiDi(wsUrl: string): Promise<object>;
|
package/types/daemon.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the argv for the detached daemon child. Pure + exported so the
|
|
3
|
+
* flag-forwarding contract is unit-testable: a forward that's silently
|
|
4
|
+
* dropped here (as `--incognito` was in v0.15.0, turning `open --incognito`
|
|
5
|
+
* into a fully-authenticated session) becomes a failing test, not a leak.
|
|
6
|
+
* @param {object} opts - the session options parsed in cli.js cmdOpen
|
|
7
|
+
* @param {string} absDir - resolved output directory
|
|
8
|
+
* @param {string|undefined} initialUrl - URL to navigate on start (may be undefined)
|
|
9
|
+
* @param {string} cliPath - path to cli.js for the child to run
|
|
10
|
+
* @returns {string[]}
|
|
11
|
+
*/
|
|
12
|
+
export function buildDaemonArgs(opts: object, absDir: string, initialUrl: string | undefined, cliPath: string): string[];
|
|
1
13
|
/**
|
|
2
14
|
* Spawn a detached child process that runs the daemon.
|
|
3
15
|
* Parent polls for session.json, then exits.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a Firefox/BiDi-backed page object.
|
|
3
|
+
* @param {object} bidi - BiDi client from createBiDi()
|
|
4
|
+
* @param {object} [opts]
|
|
5
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Default prune mode.
|
|
6
|
+
* @param {{allowLocalUrls?: boolean, blockPrivateNetwork?: boolean}} [opts.urlGuard] - Navigation safety policy, applied on every goto().
|
|
7
|
+
* @param {string} [opts.uploadDir] - When set, upload() rejects files outside this dir.
|
|
8
|
+
* @param {boolean} [opts.incognito=false] - Clean session: injectCookies() is a no-op.
|
|
9
|
+
* @returns {Promise<object>} page object
|
|
10
|
+
*/
|
|
11
|
+
export function createFirefoxPage(bidi: object, opts?: {
|
|
12
|
+
pruneMode?: "act" | "browse" | "navigate" | "full" | "read" | undefined;
|
|
13
|
+
urlGuard?: {
|
|
14
|
+
allowLocalUrls?: boolean;
|
|
15
|
+
blockPrivateNetwork?: boolean;
|
|
16
|
+
} | undefined;
|
|
17
|
+
uploadDir?: string | undefined;
|
|
18
|
+
incognito?: boolean | undefined;
|
|
19
|
+
}): Promise<object>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the first available Firefox binary on the system.
|
|
3
|
+
* @returns {string} Path to the binary
|
|
4
|
+
* @throws {Error} If no Firefox is found
|
|
5
|
+
*/
|
|
6
|
+
export function findFirefox(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Launch Firefox with WebDriver BiDi enabled and return its session WS URL.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} [opts]
|
|
11
|
+
* @param {string} [opts.binary] - Firefox binary (auto-detected if omitted)
|
|
12
|
+
* @param {number} [opts.port=0] - Remote-agent port (0 = OS-assigned)
|
|
13
|
+
* @param {boolean} [opts.headed=false] - Launch with a visible window
|
|
14
|
+
* @param {string} [opts.proxy] - Proxy 'host:port' or 'scheme://host:port'
|
|
15
|
+
* (http/https → HTTP+SSL proxy; socks/socks5/socks4 → SOCKS), via prefs
|
|
16
|
+
* @param {{width:number,height:number}} [opts.viewport] - Initial window size
|
|
17
|
+
* @returns {Promise<{wsUrl: string, process: import('node:child_process').ChildProcess, port: number, ownedProfileDir: string}>}
|
|
18
|
+
*/
|
|
19
|
+
export function launchFirefox(opts?: {
|
|
20
|
+
binary?: string | undefined;
|
|
21
|
+
port?: number | undefined;
|
|
22
|
+
headed?: boolean | undefined;
|
|
23
|
+
proxy?: string | undefined;
|
|
24
|
+
viewport?: {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
} | undefined;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
wsUrl: string;
|
|
30
|
+
process: import("node:child_process").ChildProcess;
|
|
31
|
+
port: number;
|
|
32
|
+
ownedProfileDir: string;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Kill a launched Firefox and remove its temp profile dir.
|
|
36
|
+
* @param {{process: import('node:child_process').ChildProcess, ownedProfileDir?: string}} browser
|
|
37
|
+
*/
|
|
38
|
+
export function cleanupFirefox(browser: {
|
|
39
|
+
process: import("node:child_process").ChildProcess;
|
|
40
|
+
ownedProfileDir?: string;
|
|
41
|
+
}): Promise<void>;
|
package/types/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* @param {object} [opts]
|
|
7
7
|
* @param {'headless'|'headed'|'hybrid'} [opts.mode='headless'] - Browser mode
|
|
8
8
|
* @param {boolean} [opts.cookies=true] - Inject user's cookies (Phase 2)
|
|
9
|
+
* @param {boolean} [opts.incognito=false] - Clean, unauthenticated session:
|
|
10
|
+
* skip cookie/auth injection entirely (equivalent to cookies:false here).
|
|
9
11
|
* @param {boolean} [opts.prune=true] - Apply ARIA pruning (Phase 2)
|
|
10
12
|
* @param {number} [opts.timeout=30000] - Navigation timeout in ms
|
|
11
13
|
* @param {boolean} [opts.blockAds=true] - Block ~120 common ad/tracker
|
|
@@ -27,8 +29,9 @@
|
|
|
27
29
|
* @returns {Promise<string>} ARIA snapshot text
|
|
28
30
|
*/
|
|
29
31
|
export function browse(url: string, opts?: {
|
|
30
|
-
mode?: "
|
|
32
|
+
mode?: "headed" | "headless" | "hybrid" | undefined;
|
|
31
33
|
cookies?: boolean | undefined;
|
|
34
|
+
incognito?: boolean | undefined;
|
|
32
35
|
prune?: boolean | undefined;
|
|
33
36
|
timeout?: number | undefined;
|
|
34
37
|
blockAds?: boolean | undefined;
|
|
@@ -83,11 +86,17 @@ export function browse(url: string, opts?: {
|
|
|
83
86
|
* @param {boolean} [opts.consent=true] - Auto-dismiss cookie consent dialogs.
|
|
84
87
|
* @param {string} [opts.storageState] - Path to a storage-state JSON file
|
|
85
88
|
* (cookies + localStorage) to load before navigation.
|
|
89
|
+
* @param {boolean} [opts.incognito=false] - Clean, unauthenticated session:
|
|
90
|
+
* skip storageState loading and make injectCookies() a no-op, so no auth
|
|
91
|
+
* ever enters the session even if a caller injects unconditionally.
|
|
86
92
|
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Pruning mode.
|
|
93
|
+
* @param {'chromium'|'firefox'} [opts.engine='chromium'] - Browser engine.
|
|
94
|
+
* 'firefox' drives over WebDriver BiDi (a separate transport / page object);
|
|
95
|
+
* 'chromium' (default) uses CDP.
|
|
87
96
|
* @returns {Promise<object>} Page handle with goto, snapshot, close
|
|
88
97
|
*/
|
|
89
98
|
export function connect(opts?: {
|
|
90
|
-
mode?: "
|
|
99
|
+
mode?: "headed" | "headless" | "hybrid" | undefined;
|
|
91
100
|
port?: number | undefined;
|
|
92
101
|
downloadPath?: string | undefined;
|
|
93
102
|
blockAds?: boolean | undefined;
|
|
@@ -104,7 +113,9 @@ export function connect(opts?: {
|
|
|
104
113
|
} | undefined;
|
|
105
114
|
consent?: boolean | undefined;
|
|
106
115
|
storageState?: string | undefined;
|
|
116
|
+
incognito?: boolean | undefined;
|
|
107
117
|
pruneMode?: "act" | "browse" | "navigate" | "full" | "read" | undefined;
|
|
118
|
+
engine?: "chromium" | "firefox" | undefined;
|
|
108
119
|
}): Promise<object>;
|
|
109
120
|
/**
|
|
110
121
|
* Apply Network.setBlockedURLs for ad/tracker blocking on a session.
|
package/types/readable.d.ts
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
* @returns {string}
|
|
8
8
|
*/
|
|
9
9
|
export function formatReadable(r: object): string;
|
|
10
|
+
/**
|
|
11
|
+
* Turn the raw in-page extraction result into the public readable() shape,
|
|
12
|
+
* applying the advisory confidence rule. Transport-agnostic.
|
|
13
|
+
* @param {object} r - raw result from EXTRACT_EXPRESSION
|
|
14
|
+
* @returns {object} public readable() result
|
|
15
|
+
*/
|
|
16
|
+
export function finalizeReadable(r: object): object;
|
|
10
17
|
/**
|
|
11
18
|
* Extract the main article from the current page.
|
|
12
19
|
* @param {object} session - CDP session-scoped handle (.send()).
|
|
@@ -16,3 +23,4 @@ export function formatReadable(r: object): string;
|
|
|
16
23
|
* confidence: 'high'|'low', readerable, hint? } — extracted article
|
|
17
24
|
*/
|
|
18
25
|
export function readable(session: object): Promise<object>;
|
|
26
|
+
export const EXTRACT_EXPRESSION: string;
|