barebrowse 0.13.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/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 (opts.cookies !== false) {
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 (opts.cookies !== false) {
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
- if (opts.storageState) {
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
- await authenticate(page.session, url, { browser: cookieOpts?.browser });
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
- await authenticate(tab.session, url, { browser: cookieOpts?.browser });
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
- * Extract the main article from the current page.
76
- * @param {object} session - CDP session-scoped handle (.send()).
77
- * @returns {Promise<object>} One of:
78
- * { ok: false, hint } — no article content found
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 async function readable(session) {
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
- byline: r.byline,
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 extractCookies + injectCookies.
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";
@@ -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?: "headless" | "headed" | "hybrid" | undefined;
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?: "headless" | "headed" | "hybrid" | undefined;
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.
@@ -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;