@the-forge-flow/camoufox-pi 0.1.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/LICENSE +21 -0
- package/README.md +209 -0
- package/dist/client/camoufox-client.d.ts +62 -0
- package/dist/client/camoufox-client.d.ts.map +1 -0
- package/dist/client/camoufox-client.js +277 -0
- package/dist/client/camoufox-client.js.map +1 -0
- package/dist/client/launcher.d.ts +28 -0
- package/dist/client/launcher.d.ts.map +1 -0
- package/dist/client/launcher.js +30 -0
- package/dist/client/launcher.js.map +1 -0
- package/dist/client/signal.d.ts +6 -0
- package/dist/client/signal.d.ts.map +1 -0
- package/dist/client/signal.js +29 -0
- package/dist/client/signal.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/types.d.ts +13 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +56 -0
- package/dist/errors.js.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -0
- package/dist/search/adapters/duckduckgo.d.ts +3 -0
- package/dist/search/adapters/duckduckgo.d.ts.map +1 -0
- package/dist/search/adapters/duckduckgo.js +52 -0
- package/dist/search/adapters/duckduckgo.js.map +1 -0
- package/dist/search/types.d.ts +16 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +2 -0
- package/dist/search/types.js.map +1 -0
- package/dist/security/ssrf.d.ts +6 -0
- package/dist/security/ssrf.d.ts.map +1 -0
- package/dist/security/ssrf.js +82 -0
- package/dist/security/ssrf.js.map +1 -0
- package/dist/services/camoufox-service.d.ts +19 -0
- package/dist/services/camoufox-service.d.ts.map +1 -0
- package/dist/services/camoufox-service.js +49 -0
- package/dist/services/camoufox-service.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +11 -0
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +53 -0
- package/dist/tools/fetch-url.js.map +1 -0
- package/dist/tools/formats.d.ts +2 -0
- package/dist/tools/formats.d.ts.map +1 -0
- package/dist/tools/formats.js +12 -0
- package/dist/tools/formats.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/search-web.d.ts +11 -0
- package/dist/tools/search-web.d.ts.map +1 -0
- package/dist/tools/search-web.js +55 -0
- package/dist/tools/search-web.js.map +1 -0
- package/dist/tools/types.d.ts +22 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/update-check.d.ts +24 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +94 -0
- package/dist/update-check.js.map +1 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MonsieurBarti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/MonsieurBarti/The-Forge-Flow-CC/refs/heads/main/assets/forge-banner.png" alt="The Forge Flow" width="100%">
|
|
3
|
+
|
|
4
|
+
<h1>@the-forge-flow/camoufox-pi</h1>
|
|
5
|
+
|
|
6
|
+
<p>
|
|
7
|
+
<strong>Stealth web search and URL fetching for the PI coding agent</strong>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://github.com/MonsieurBarti/camoufox-pi/actions/workflows/ci.yml">
|
|
12
|
+
<img src="https://img.shields.io/github/actions/workflow/status/MonsieurBarti/camoufox-pi/ci.yml?label=CI&style=flat-square" alt="CI Status">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/@the-forge-flow/camoufox-pi">
|
|
15
|
+
<img src="https://img.shields.io/npm/v/@the-forge-flow/camoufox-pi?style=flat-square" alt="npm version">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="LICENSE">
|
|
18
|
+
<img src="https://img.shields.io/github/license/MonsieurBarti/camoufox-pi?style=flat-square" alt="License">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What it does
|
|
26
|
+
|
|
27
|
+
PI extension that wraps [Camoufox](https://github.com/daijro/camoufox) — a Firefox fork patched at the C++ level for anti-fingerprint resistance — to give the coding agent a stealth-capable web search and URL fetcher. For sites that block conventional headless browsers (Cloudflare, DataDome, PerimeterX, Turnstile, Google's bot wall, LinkedIn, etc.).
|
|
28
|
+
|
|
29
|
+
Sibling to [`@the-forge-flow/lightpanda-pi`](https://github.com/MonsieurBarti/lightpanda-pi). Where `lightpanda-pi` is the fast/light choice for cooperative targets, `camoufox-pi` is the choice when sites actively block bots. Camoufox patches fingerprint surfaces inside SpiderMonkey / Gecko C++, before JavaScript can observe them — fundamentally more robust than runtime JS-injection approaches like `puppeteer-extra-plugin-stealth`. Independent benchmarks report ~100% bypass rate vs ~33% for Playwright-Chromium.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- `tff-fetch_url` — fetch a URL via stealth Firefox and return HTML
|
|
34
|
+
- `tff-search_web` — web search via DuckDuckGo (Google lands in a follow-up slice)
|
|
35
|
+
- **Stealth properties** — C++-level fingerprint spoofing, patched canvas/WebGL, Juggler (Firefox remote) protocol — not CDP
|
|
36
|
+
- **SSRF protection** — private IP ranges, link-local, loopback, cloud metadata, and CGNAT blocked pre-navigation
|
|
37
|
+
- **Scheme allow-list** — only `http:` / `https:` accepted at the tool boundary; `file:`, `javascript:`, `data:`, `chrome://` rejected
|
|
38
|
+
- **Response size caps** — UTF-8-safe truncation at `max_bytes` (default 2 MiB, max 50 MiB) with `truncated` flag
|
|
39
|
+
- **`isolate: true` opt-in** — one-shot browser context per call, no cookie/storage bleed
|
|
40
|
+
- **Lazy binary download** — ~500 MB Camoufox binary fetched on first use, not install
|
|
41
|
+
|
|
42
|
+
## Trade-offs vs lightpanda-pi
|
|
43
|
+
|
|
44
|
+
| | lightpanda-pi | camoufox-pi |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| RSS per session | ~50 MB | 200–1300 MB |
|
|
47
|
+
| Binary size | few MB | 300–700 MB (lazy download on first use) |
|
|
48
|
+
| Cold start | instant | 1–3 s |
|
|
49
|
+
| Cooperative sites | yes | yes |
|
|
50
|
+
| WAF-protected sites | no | yes |
|
|
51
|
+
| Canvas/WebGL | not rendered | spoofed (except post-2026-03 regression — see SPEC §17) |
|
|
52
|
+
| TLS/JA3 fingerprint | libcurl | Firefox (unspoofed — use proxy if target fingerprints TLS) |
|
|
53
|
+
|
|
54
|
+
Pick one based on target profile. They can coexist but share no runtime.
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- Node.js >= 22.5.0
|
|
59
|
+
- PI (`pi` CLI) installed
|
|
60
|
+
- ~500 MB disk space for the Camoufox binary (lazy downloaded on first use)
|
|
61
|
+
- macOS or Linux (Windows untested)
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# From npm (recommended)
|
|
67
|
+
pi install npm:@the-forge-flow/camoufox-pi
|
|
68
|
+
|
|
69
|
+
# Project-local only
|
|
70
|
+
pi install -l npm:@the-forge-flow/camoufox-pi
|
|
71
|
+
|
|
72
|
+
# From GitHub (tracks main)
|
|
73
|
+
pi install git:github.com/MonsieurBarti/camoufox-pi
|
|
74
|
+
|
|
75
|
+
# Pin a version
|
|
76
|
+
pi install npm:@the-forge-flow/camoufox-pi@0.1.0
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then reload PI with `/reload` (or restart it). First tool call downloads the Camoufox binary (~500 MB, one-time).
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
### Tools
|
|
84
|
+
|
|
85
|
+
| Tool | Description | Key parameters |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `tff-fetch_url` | Fetch a URL via stealth Firefox, return HTML | `url`, `timeout_ms`, `max_bytes`, `isolate` |
|
|
88
|
+
| `tff-search_web` | Web search (DuckDuckGo) | `query`, `max_results`, `timeout_ms`, `isolate` |
|
|
89
|
+
|
|
90
|
+
**`tff-fetch_url`** returns `{ url, finalUrl, status, html, bytes, truncated }`. HTML is returned raw (no markdown conversion in v0.1.0). `truncated: true` means the response exceeded `max_bytes` and was cut at a UTF-8-safe boundary.
|
|
91
|
+
|
|
92
|
+
**`tff-search_web`** returns `{ engine, query, results[], atLimit }` where each result is `{ title, url, snippet, rank }`. `atLimit` means `results.length === max_results` — could mean DDG had more, or exactly that many. No ground-truth "has_more" signal is available from the engine.
|
|
93
|
+
|
|
94
|
+
### Security
|
|
95
|
+
|
|
96
|
+
- **Scheme allow-list.** Only `http:` and `https:` accepted at the tool boundary. `file:`, `javascript:`, `data:`, `chrome://` and similar are rejected before any navigation.
|
|
97
|
+
- **SSRF protection.** Targets that resolve to private IP ranges (loopback, RFC1918, link-local, cloud metadata 169.254.169.254, CGNAT, IPv6 ULAs) are rejected pre-navigation. No opt-out in v0.1.0.
|
|
98
|
+
- **Response truncation.** Bodies exceeding `max_bytes` are cut at a UTF-8-safe byte boundary and flagged `truncated: true`. Default 2 MiB, max 50 MiB.
|
|
99
|
+
- **Untrusted content.** The `tff-fetch_url` prompt guidelines explicitly warn the LLM that fetched HTML is UNTRUSTED and must not be executed, eval'd, or treated as authoritative instructions.
|
|
100
|
+
- **`isolate: true`** for sensitive fetches — fresh `BrowserContext` per call, no cookie/storage reuse with the shared session context.
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
v0.1.0 does **not** load a config file. All configuration is per-call via tool parameters. A layered config (project-local + user-global + env + fs.watch reload) lands in a later slice.
|
|
105
|
+
|
|
106
|
+
Defaults baked into `DEFAULT_CONFIG`:
|
|
107
|
+
|
|
108
|
+
| Field | Default | Description |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `timeoutMs` | `30000` | Per-navigation timeout (ms), overridable via `timeout_ms` |
|
|
111
|
+
| `maxBytes` | `2097152` | 2 MiB response cap for `fetch_url`, overridable via `max_bytes` |
|
|
112
|
+
| `defaultEngine` | `"duckduckgo"` | Only valid value in v0.1.0 |
|
|
113
|
+
|
|
114
|
+
## Architecture
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
┌─────────────────────┐
|
|
118
|
+
│ PI host process │
|
|
119
|
+
│ └─ loads extension │
|
|
120
|
+
└─────────┬────────────┘
|
|
121
|
+
│ session_start
|
|
122
|
+
▼
|
|
123
|
+
┌──────────────────────────────────────────────────────┐
|
|
124
|
+
│ camoufox-pi extension (in PI process) │
|
|
125
|
+
│ │
|
|
126
|
+
│ CamoufoxService (singleton) │
|
|
127
|
+
│ └─ CamoufoxClient │
|
|
128
|
+
│ ├─ one Browser │
|
|
129
|
+
│ ├─ one BrowserContext (cookies persist) │
|
|
130
|
+
│ └─ Launcher (camoufox-js, isolated) │
|
|
131
|
+
│ │
|
|
132
|
+
│ Tools: tff-fetch_url / tff-search_web ─┐ │
|
|
133
|
+
│ ▼ │
|
|
134
|
+
│ CamoufoxClient.navigate │
|
|
135
|
+
└──────────────────────────────────────────────────────┘
|
|
136
|
+
│ Playwright (Juggler)
|
|
137
|
+
▼
|
|
138
|
+
┌──────────────────────┐
|
|
139
|
+
│ Camoufox process │
|
|
140
|
+
│ (patched Firefox) │
|
|
141
|
+
└──────────────────────┘
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Launcher isolation.** `src/client/launcher.ts` is the **only** file that imports `camoufox-js`. Every other file uses the `Launcher` interface. This keeps the third-party Node wrapper (Apify's port of Python-official Camoufox) swappable — a future official binding, patchright, or a Python subprocess slots in with a one-file change.
|
|
145
|
+
|
|
146
|
+
**Fake-launcher test seam.** `tests/helpers/fake-launcher.ts` injects a stub `BrowserContext` so every unit test runs without downloading the ~500 MB binary or spawning a real Firefox.
|
|
147
|
+
|
|
148
|
+
Key components in `src/`:
|
|
149
|
+
|
|
150
|
+
| File | Purpose |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `src/index.ts` | Extension factory — session lifecycle, tool/command/hook registration |
|
|
153
|
+
| `src/services/camoufox-service.ts` | Singleton service owning the `CamoufoxClient`, kicks off `ensureReady()` from `session_start` |
|
|
154
|
+
| `src/client/camoufox-client.ts` | Lifecycle + `navigate` + `fetchUrl` + `search` + `close` |
|
|
155
|
+
| `src/client/launcher.ts` | `Launcher` interface + `RealLauncher` (sole `camoufox-js` importer) |
|
|
156
|
+
| `src/client/signal.ts` | `combineSignals(external, timeoutMs)` — turn-signal + timeout composition |
|
|
157
|
+
| `src/errors.ts` | `CamoufoxError` discriminated union + `CamoufoxErrorBox` + `mapPlaywrightError` |
|
|
158
|
+
| `src/security/ssrf.ts` | Private-IP + link-local + cloud-metadata blocklist, pre-navigation |
|
|
159
|
+
| `src/search/adapters/duckduckgo.ts` | DOM-query SERP parser against `html.duckduckgo.com` |
|
|
160
|
+
| `src/tools/fetch-url.ts` | `tff-fetch_url` tool definition |
|
|
161
|
+
| `src/tools/search-web.ts` | `tff-search_web` tool definition |
|
|
162
|
+
| `src/tools/formats.ts` | TypeBox `format: "uri"` scheme allow-list hook |
|
|
163
|
+
| `src/tools/types.ts` | `ToolDefinition` structural interface |
|
|
164
|
+
| `src/types.ts` | `CamoufoxConfig` + `DEFAULT_CONFIG` |
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
bun install # install deps
|
|
170
|
+
bun run test # vitest once
|
|
171
|
+
bun run test:watch # vitest watch mode
|
|
172
|
+
bun run test:coverage # v8 coverage
|
|
173
|
+
bun run lint # biome check
|
|
174
|
+
bun run lint:fix # auto-fix
|
|
175
|
+
bun run build # tsc → dist/
|
|
176
|
+
bun run typecheck # type-only check
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Pre-commit hooks (lefthook) run biome, typecheck, and tests in parallel.
|
|
180
|
+
|
|
181
|
+
Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/) — enforced by commitlint.
|
|
182
|
+
|
|
183
|
+
## Known limitations
|
|
184
|
+
|
|
185
|
+
v0.1.0 is the foundational slice. The following are deliberately deferred to later slices:
|
|
186
|
+
|
|
187
|
+
- **DuckDuckGo only.** Google / Brave / Bing adapters land in follow-up slices; Google requires stealth tuning that deserves its own slice.
|
|
188
|
+
- **No retries.** `network`, `timeout`, `playwright_disconnected`, and `browser_crashed` surface as errors — no exponential backoff.
|
|
189
|
+
- **No cache.** No in-memory LRU, no on-disk cache, no request coalescing.
|
|
190
|
+
- **No blocked-detection.** CF/DataDome/PerimeterX challenge pages return as HTTP 200 with challenge HTML; no structured `{ type: "blocked" }` yet.
|
|
191
|
+
- **No observability.** No metrics, no event-bus events, no span IDs. `binary_download_progress` not emitted (only `console.debug`).
|
|
192
|
+
- **No config layering.** No config file, no env vars, no `fs.watch` reload. Per-call params and `DEFAULT_CONFIG` only.
|
|
193
|
+
- **TLS/JA3 fingerprint not spoofed.** Camoufox inherits Firefox's ClientHello. Targets that fingerprint TLS (aggressive DataDome, Akamai Bot Manager tier 3) will still detect. Mitigation deferred to a proxy-integration slice.
|
|
194
|
+
- **Sticky launch failure.** A failed `ensureReady()` marks the client permanently failed. Retrying requires reconstructing the service. Auto-reconnect lands in the retry-and-reconnect slice.
|
|
195
|
+
- **Third-party Node wrapper.** Upstream Camoufox endorses only the Python wrapper. `camoufox-js` (Apify, MPL-2.0) is the Node port; launcher isolation keeps it swappable.
|
|
196
|
+
|
|
197
|
+
Detailed design and deferred-feature landing plan live in local-only `docs/` (not published).
|
|
198
|
+
|
|
199
|
+
## Contributing
|
|
200
|
+
|
|
201
|
+
1. Fork the repository
|
|
202
|
+
2. Create your feature branch (`git checkout -b feature/amazing`)
|
|
203
|
+
3. Commit with conventional commits (`git commit -m "feat: add something"`)
|
|
204
|
+
4. Push to the branch (`git push origin feature/amazing`)
|
|
205
|
+
5. Open a Pull Request
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Browser, BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import type { RawResult } from "../search/types.js";
|
|
3
|
+
import { type LookupFn } from "../security/ssrf.js";
|
|
4
|
+
import type { CamoufoxConfig } from "../types.js";
|
|
5
|
+
import type { Launcher } from "./launcher.js";
|
|
6
|
+
export interface CamoufoxClientOptions {
|
|
7
|
+
readonly launcher: Launcher;
|
|
8
|
+
readonly config?: CamoufoxConfig;
|
|
9
|
+
/** Optional DNS lookup override; used to inject stubs in tests. */
|
|
10
|
+
readonly ssrfLookup?: LookupFn;
|
|
11
|
+
}
|
|
12
|
+
export declare class CamoufoxClient {
|
|
13
|
+
private readonly launcher;
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly ssrfLookup;
|
|
16
|
+
private state;
|
|
17
|
+
constructor(opts: CamoufoxClientOptions);
|
|
18
|
+
isAlive(): boolean;
|
|
19
|
+
ensureReady(signal?: AbortSignal): Promise<void>;
|
|
20
|
+
fetchUrl(url: string, opts: {
|
|
21
|
+
signal: AbortSignal;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
maxBytes?: number;
|
|
24
|
+
isolate?: boolean;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
html: string;
|
|
27
|
+
status: number;
|
|
28
|
+
finalUrl: string;
|
|
29
|
+
bytes: number;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
search(query: string, opts: {
|
|
33
|
+
signal: AbortSignal;
|
|
34
|
+
maxResults?: number;
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
isolate?: boolean;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
results: RawResult[];
|
|
39
|
+
engine: "duckduckgo";
|
|
40
|
+
query: string;
|
|
41
|
+
}>;
|
|
42
|
+
protected navigate(url: string, opts: {
|
|
43
|
+
signal: AbortSignal;
|
|
44
|
+
timeoutMs: number;
|
|
45
|
+
waitUntil: "load" | "domcontentloaded" | "networkidle";
|
|
46
|
+
isolate?: boolean;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
page: Page;
|
|
49
|
+
response: {
|
|
50
|
+
status(): number;
|
|
51
|
+
url(): string;
|
|
52
|
+
};
|
|
53
|
+
cleanup: () => void;
|
|
54
|
+
}>;
|
|
55
|
+
close(): Promise<void>;
|
|
56
|
+
protected getConfig(): CamoufoxConfig;
|
|
57
|
+
protected getContext(): BrowserContext;
|
|
58
|
+
protected getBrowser(): Browser;
|
|
59
|
+
private doLaunch;
|
|
60
|
+
private awaitWithSignal;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=camoufox-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camoufox-client.d.ts","sourceRoot":"","sources":["../../src/client/camoufox-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAIrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,KAAK,QAAQ,EAAoB,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAY9C,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC;CAC/B;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,KAAK,CAAkC;gBAEnC,IAAI,EAAE,qBAAqB;IAMvC,OAAO,IAAI,OAAO;IAIZ,WAAW,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAehD,QAAQ,CACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;QACL,MAAM,EAAE,WAAW,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,OAAO,CAAC;KAClB,GACC,OAAO,CAAC;QACV,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,OAAO,CAAC;KACnB,CAAC;IAqEI,MAAM,CACX,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;QACL,MAAM,EAAE,WAAW,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KAClB,GACC,OAAO,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;cAiDzD,QAAQ,CACvB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;QACL,MAAM,EAAE,WAAW,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;QACvD,OAAO,CAAC,EAAE,OAAO,CAAC;KAClB,GACC,OAAO,CAAC;QACV,IAAI,EAAE,IAAI,CAAC;QACX,QAAQ,EAAE;YAAE,MAAM,IAAI,MAAM,CAAC;YAAC,GAAG,IAAI,MAAM,CAAA;SAAE,CAAC;QAC9C,OAAO,EAAE,MAAM,IAAI,CAAC;KACpB,CAAC;IAuDI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,SAAS,CAAC,SAAS,IAAI,cAAc;IAIrC,SAAS,CAAC,UAAU,IAAI,cAAc;IAUtC,SAAS,CAAC,UAAU,IAAI,OAAO;YAUjB,QAAQ;YAsBR,eAAe;CAqB7B"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { CamoufoxErrorBox, mapPlaywrightError } from "../errors.js";
|
|
2
|
+
import { duckduckgoAdapter } from "../search/adapters/duckduckgo.js";
|
|
3
|
+
import { assertSafeTarget } from "../security/ssrf.js";
|
|
4
|
+
import { DEFAULT_CONFIG } from "../types.js";
|
|
5
|
+
import { combineSignals } from "./signal.js";
|
|
6
|
+
export class CamoufoxClient {
|
|
7
|
+
launcher;
|
|
8
|
+
config;
|
|
9
|
+
ssrfLookup;
|
|
10
|
+
state = { status: "idle" };
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.launcher = opts.launcher;
|
|
13
|
+
this.config = opts.config ?? DEFAULT_CONFIG;
|
|
14
|
+
this.ssrfLookup = opts.ssrfLookup;
|
|
15
|
+
}
|
|
16
|
+
isAlive() {
|
|
17
|
+
return this.state.status === "ready" && this.state.browser?.isConnected() === true;
|
|
18
|
+
}
|
|
19
|
+
async ensureReady(signal) {
|
|
20
|
+
if (this.state.status === "ready")
|
|
21
|
+
return;
|
|
22
|
+
if (this.state.status === "failed" && this.state.error)
|
|
23
|
+
throw this.state.error;
|
|
24
|
+
if (this.state.status === "closed") {
|
|
25
|
+
throw new CamoufoxErrorBox({ type: "playwright_disconnected" });
|
|
26
|
+
}
|
|
27
|
+
if (this.state.status === "launching" && this.state.launchPromise) {
|
|
28
|
+
await this.awaitWithSignal(this.state.launchPromise, signal);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const launchPromise = this.doLaunch();
|
|
32
|
+
this.state = { status: "launching", launchPromise };
|
|
33
|
+
await this.awaitWithSignal(launchPromise, signal);
|
|
34
|
+
}
|
|
35
|
+
async fetchUrl(url, opts) {
|
|
36
|
+
await this.ensureReady(opts.signal);
|
|
37
|
+
if (opts.timeoutMs !== undefined &&
|
|
38
|
+
(!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 1_000 || opts.timeoutMs > 120_000)) {
|
|
39
|
+
throw new CamoufoxErrorBox({
|
|
40
|
+
type: "config_invalid",
|
|
41
|
+
field: "timeoutMs",
|
|
42
|
+
reason: `must be integer in [1000, 120000], got ${opts.timeoutMs}`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (opts.maxBytes !== undefined &&
|
|
46
|
+
(!Number.isInteger(opts.maxBytes) || opts.maxBytes < 1_024 || opts.maxBytes > 52_428_800)) {
|
|
47
|
+
throw new CamoufoxErrorBox({
|
|
48
|
+
type: "config_invalid",
|
|
49
|
+
field: "maxBytes",
|
|
50
|
+
reason: `must be integer in [1024, 52428800], got ${opts.maxBytes}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await assertSafeTarget(url, this.ssrfLookup ? { lookup: this.ssrfLookup } : {});
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
throw new CamoufoxErrorBox({
|
|
58
|
+
type: "config_invalid",
|
|
59
|
+
field: "url",
|
|
60
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const navOpts = {
|
|
64
|
+
signal: opts.signal,
|
|
65
|
+
timeoutMs: opts.timeoutMs ?? this.config.timeoutMs,
|
|
66
|
+
waitUntil: "load",
|
|
67
|
+
};
|
|
68
|
+
if (opts.isolate !== undefined)
|
|
69
|
+
navOpts.isolate = opts.isolate;
|
|
70
|
+
const { page, response, cleanup } = await this.navigate(url, navOpts);
|
|
71
|
+
try {
|
|
72
|
+
const rawHtml = await page.content();
|
|
73
|
+
const maxBytes = opts.maxBytes ?? this.config.maxBytes;
|
|
74
|
+
const rawBytes = Buffer.byteLength(rawHtml, "utf8");
|
|
75
|
+
let html = rawHtml;
|
|
76
|
+
let bytes = rawBytes;
|
|
77
|
+
let truncated = false;
|
|
78
|
+
if (rawBytes > maxBytes) {
|
|
79
|
+
const buf = Buffer.from(rawHtml, "utf8");
|
|
80
|
+
html = buf.subarray(0, maxBytes).toString("utf8");
|
|
81
|
+
bytes = Buffer.byteLength(html, "utf8");
|
|
82
|
+
truncated = true;
|
|
83
|
+
}
|
|
84
|
+
return { html, status: response.status(), finalUrl: response.url(), bytes, truncated };
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (opts.signal.aborted) {
|
|
88
|
+
throw new CamoufoxErrorBox({ type: "aborted" });
|
|
89
|
+
}
|
|
90
|
+
if (err instanceof CamoufoxErrorBox)
|
|
91
|
+
throw err;
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
cleanup();
|
|
96
|
+
await page.close().catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async search(query, opts) {
|
|
100
|
+
await this.ensureReady(opts.signal);
|
|
101
|
+
const maxResults = opts.maxResults ?? 10;
|
|
102
|
+
if (!Number.isInteger(maxResults) || maxResults < 1 || maxResults > 50) {
|
|
103
|
+
throw new CamoufoxErrorBox({
|
|
104
|
+
type: "config_invalid",
|
|
105
|
+
field: "maxResults",
|
|
106
|
+
reason: `must be integer in [1, 50], got ${maxResults}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (opts.timeoutMs !== undefined &&
|
|
110
|
+
(!Number.isInteger(opts.timeoutMs) || opts.timeoutMs < 1_000 || opts.timeoutMs > 120_000)) {
|
|
111
|
+
throw new CamoufoxErrorBox({
|
|
112
|
+
type: "config_invalid",
|
|
113
|
+
field: "timeoutMs",
|
|
114
|
+
reason: `must be integer in [1000, 120000], got ${opts.timeoutMs}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const adapter = duckduckgoAdapter;
|
|
118
|
+
const url = adapter.buildUrl(query);
|
|
119
|
+
const navOpts = {
|
|
120
|
+
signal: opts.signal,
|
|
121
|
+
timeoutMs: opts.timeoutMs ?? this.config.timeoutMs,
|
|
122
|
+
waitUntil: adapter.waitStrategy.readyState,
|
|
123
|
+
};
|
|
124
|
+
if (opts.isolate !== undefined)
|
|
125
|
+
navOpts.isolate = opts.isolate;
|
|
126
|
+
const { page, cleanup } = await this.navigate(url, navOpts);
|
|
127
|
+
try {
|
|
128
|
+
const results = await adapter.parseResults(page, maxResults);
|
|
129
|
+
return { results, engine: "duckduckgo", query };
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (opts.signal.aborted) {
|
|
133
|
+
throw new CamoufoxErrorBox({ type: "aborted" });
|
|
134
|
+
}
|
|
135
|
+
if (err instanceof CamoufoxErrorBox)
|
|
136
|
+
throw err;
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
cleanup();
|
|
141
|
+
await page.close().catch(() => undefined);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async navigate(url, opts) {
|
|
145
|
+
let context;
|
|
146
|
+
let ownContext = false;
|
|
147
|
+
if (opts.isolate) {
|
|
148
|
+
const browser = this.getBrowser();
|
|
149
|
+
context = await browser.newContext();
|
|
150
|
+
ownContext = true;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
context = this.getContext();
|
|
154
|
+
}
|
|
155
|
+
const combined = combineSignals(opts.signal, opts.timeoutMs);
|
|
156
|
+
const page = await context.newPage();
|
|
157
|
+
const abortHandler = () => {
|
|
158
|
+
page.close().catch(() => undefined);
|
|
159
|
+
};
|
|
160
|
+
combined.signal.addEventListener("abort", abortHandler, { once: true });
|
|
161
|
+
const cleanup = () => {
|
|
162
|
+
combined.signal.removeEventListener("abort", abortHandler);
|
|
163
|
+
combined.cleanup();
|
|
164
|
+
if (ownContext) {
|
|
165
|
+
context.close().catch(() => undefined);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const started = Date.now();
|
|
169
|
+
try {
|
|
170
|
+
const response = await page.goto(url, {
|
|
171
|
+
timeout: opts.timeoutMs,
|
|
172
|
+
waitUntil: opts.waitUntil,
|
|
173
|
+
});
|
|
174
|
+
if (!response) {
|
|
175
|
+
throw new CamoufoxErrorBox({
|
|
176
|
+
type: "network",
|
|
177
|
+
cause: "goto returned null",
|
|
178
|
+
url,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const status = response.status();
|
|
182
|
+
if (status >= 400) {
|
|
183
|
+
throw new CamoufoxErrorBox({ type: "http", status, url: response.url() });
|
|
184
|
+
}
|
|
185
|
+
return { page, response, cleanup };
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
cleanup();
|
|
189
|
+
await page.close().catch(() => undefined);
|
|
190
|
+
if (err instanceof CamoufoxErrorBox)
|
|
191
|
+
throw err;
|
|
192
|
+
const mapped = mapPlaywrightError(err, {
|
|
193
|
+
url,
|
|
194
|
+
phase: "nav",
|
|
195
|
+
elapsedMs: Date.now() - started,
|
|
196
|
+
signal: opts.signal,
|
|
197
|
+
});
|
|
198
|
+
throw new CamoufoxErrorBox(mapped);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async close() {
|
|
202
|
+
const browser = this.state.browser;
|
|
203
|
+
this.state = { status: "closed" };
|
|
204
|
+
if (browser) {
|
|
205
|
+
try {
|
|
206
|
+
await browser.close();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// browser already dead — ignore
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
getConfig() {
|
|
214
|
+
return this.config;
|
|
215
|
+
}
|
|
216
|
+
getContext() {
|
|
217
|
+
if (this.state.status !== "ready" || !this.state.context) {
|
|
218
|
+
throw new CamoufoxErrorBox({ type: "playwright_disconnected" });
|
|
219
|
+
}
|
|
220
|
+
if (this.state.browser?.isConnected() !== true) {
|
|
221
|
+
throw new CamoufoxErrorBox({ type: "playwright_disconnected" });
|
|
222
|
+
}
|
|
223
|
+
return this.state.context;
|
|
224
|
+
}
|
|
225
|
+
getBrowser() {
|
|
226
|
+
if (this.state.status !== "ready" || !this.state.browser) {
|
|
227
|
+
throw new CamoufoxErrorBox({ type: "playwright_disconnected" });
|
|
228
|
+
}
|
|
229
|
+
if (!this.state.browser.isConnected()) {
|
|
230
|
+
throw new CamoufoxErrorBox({ type: "playwright_disconnected" });
|
|
231
|
+
}
|
|
232
|
+
return this.state.browser;
|
|
233
|
+
}
|
|
234
|
+
async doLaunch() {
|
|
235
|
+
try {
|
|
236
|
+
const { browser, context, version } = await this.launcher.launch();
|
|
237
|
+
// If close() was called while we were launching, tear down the fresh
|
|
238
|
+
// browser instead of resurrecting into ready. Leaves state as "closed".
|
|
239
|
+
if (this.state.status !== "launching") {
|
|
240
|
+
await browser.close().catch(() => undefined);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
this.state = { status: "ready", browser, context, version };
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
const stderr = err instanceof Error ? err.message : String(err);
|
|
247
|
+
const boxed = new CamoufoxErrorBox({ type: "browser_launch_failed", stderr });
|
|
248
|
+
// If close() was called during the failed launch, keep the closed state
|
|
249
|
+
// (don't overwrite it with failed — closed is terminal).
|
|
250
|
+
if (this.state.status === "launching") {
|
|
251
|
+
this.state = { status: "failed", error: boxed };
|
|
252
|
+
}
|
|
253
|
+
throw boxed;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async awaitWithSignal(p, signal) {
|
|
257
|
+
if (!signal)
|
|
258
|
+
return p;
|
|
259
|
+
if (signal.aborted)
|
|
260
|
+
throw new CamoufoxErrorBox({ type: "aborted" });
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
const onAbort = () => {
|
|
263
|
+
signal.removeEventListener("abort", onAbort);
|
|
264
|
+
reject(new CamoufoxErrorBox({ type: "aborted" }));
|
|
265
|
+
};
|
|
266
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
267
|
+
p.then((v) => {
|
|
268
|
+
signal.removeEventListener("abort", onAbort);
|
|
269
|
+
resolve(v);
|
|
270
|
+
}, (e) => {
|
|
271
|
+
signal.removeEventListener("abort", onAbort);
|
|
272
|
+
reject(e);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=camoufox-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camoufox-client.js","sourceRoot":"","sources":["../../src/client/camoufox-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Browser, type BrowserContext } from "playwright-core";
|
|
2
|
+
export interface LaunchedBrowser {
|
|
3
|
+
readonly browser: Browser;
|
|
4
|
+
readonly context: BrowserContext;
|
|
5
|
+
readonly version: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Launcher {
|
|
8
|
+
launch(): Promise<LaunchedBrowser>;
|
|
9
|
+
}
|
|
10
|
+
export interface RealLauncherOptions {
|
|
11
|
+
/** Headless? Default: true. Override for local debugging. */
|
|
12
|
+
readonly headless?: boolean;
|
|
13
|
+
/** Override the Camoufox binary path. */
|
|
14
|
+
readonly binaryPath?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Real launcher: calls camoufox-js for fingerprint + binary-aware
|
|
18
|
+
* launch options, then drives playwright-core's firefox.launch.
|
|
19
|
+
* This is the ONLY file in the codebase that may import camoufox-js.
|
|
20
|
+
* Spec: §2, §8.
|
|
21
|
+
*/
|
|
22
|
+
export declare class RealLauncher implements Launcher {
|
|
23
|
+
private readonly headless;
|
|
24
|
+
private readonly binaryPath;
|
|
25
|
+
constructor(opts?: RealLauncherOptions);
|
|
26
|
+
launch(): Promise<LaunchedBrowser>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=launcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/client/launcher.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAW,MAAM,iBAAiB,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACxB,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,qBAAa,YAAa,YAAW,QAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;gBAEpC,IAAI,GAAE,mBAAwB;IAKpC,MAAM,IAAI,OAAO,CAAC,eAAe,CAAC;CAUxC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Launcher interface. The ONE place that may import camoufox-js.
|
|
2
|
+
// RealLauncher lands in a later task; tests use a fake via tests/helpers/fake-launcher.
|
|
3
|
+
// Spec: §2, §4.3, §8.
|
|
4
|
+
import { launchOptions as camoufoxLaunchOptions } from "camoufox-js";
|
|
5
|
+
import { firefox } from "playwright-core";
|
|
6
|
+
/**
|
|
7
|
+
* Real launcher: calls camoufox-js for fingerprint + binary-aware
|
|
8
|
+
* launch options, then drives playwright-core's firefox.launch.
|
|
9
|
+
* This is the ONLY file in the codebase that may import camoufox-js.
|
|
10
|
+
* Spec: §2, §8.
|
|
11
|
+
*/
|
|
12
|
+
export class RealLauncher {
|
|
13
|
+
headless;
|
|
14
|
+
binaryPath;
|
|
15
|
+
constructor(opts = {}) {
|
|
16
|
+
this.headless = opts.headless ?? true;
|
|
17
|
+
this.binaryPath = opts.binaryPath;
|
|
18
|
+
}
|
|
19
|
+
async launch() {
|
|
20
|
+
const launchOpts = (await camoufoxLaunchOptions({
|
|
21
|
+
headless: this.headless,
|
|
22
|
+
...(this.binaryPath !== undefined ? { executablePath: this.binaryPath } : {}),
|
|
23
|
+
}));
|
|
24
|
+
const browser = await firefox.launch(launchOpts);
|
|
25
|
+
const context = await browser.newContext();
|
|
26
|
+
const version = browser.version();
|
|
27
|
+
return { browser, context, version };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../src/client/launcher.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,wFAAwF;AACxF,sBAAsB;AAEtB,OAAO,EAAE,aAAa,IAAI,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAqC,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAmB7E;;;;;GAKG;AACH,MAAM,OAAO,YAAY;IACP,QAAQ,CAAU;IAClB,UAAU,CAAqB;IAEhD,YAAY,OAA4B,EAAE;QACzC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;QACtC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,UAAU,GAAG,CAAC,MAAM,qBAAqB,CAAC;YAC/C,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7E,CAAC,CAAyC,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IACtC,CAAC;CACD"}
|