The default GeoIP source was MaxMind GeoLite2 via sapics/ip-location-db, whose URLs kept serving the 2026-06-17 build after that project moved to GitHub Releases (found in #815). GeoIP AIO (daijro/geoip-all-in-one) resolves timezones more accurately on real proxy IPs and is rebuilt weekly. - repos.yml: AIO is the default; GeoLite2 is `deprecated: true`, with the Releases URLs from #815 so it still works when picked by name. - A cache holding a deprecated source it was not explicitly given (`camoufox set --geoip` or the GUI) moves to the default and drops the old database. An explicit choice is kept, with a FutureWarning. - needs_update() reads the database's build date instead of the file age: refresh once the build is over 8 days old, re-checking at most daily, and warn when a fresh download is over 30 days old (a frozen source). - get_geolocation(geoip_db=...) now reads that source's own database rather than the active one's, and no longer makes it the active one. - tests/test_geoip_sources.py (from #815) downloads every non-deprecated source and fails when its build is stale; tests.yml installs the geoip extra so it runs, and so gates every release. - TypeScript twin updated to match; goldens answer in both layouts. Co-authored-by: lp177 <57773165+lp177@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
camoufox (TypeScript)
This is the JavaScript/TypeScript client for Camoufox. It is a port of the
Python wrapper in ../pythonlib — it does not shell out
to Python.
The two launchers are twins: they read the same properties.json, write the
same chunked CAMOU_CONFIG, share the same browser install directory, and
ship the same fingerprint presets, font/voice lists and GeoIP configuration.
They draw the same identities too: fingerprints and WebGL devices come from a
TypeScript port of fpgen
using the same pinned model, and the per-identity draws (fonts, voices, GPU,
media devices, noise seeds) use a bit-exact port of CPython's random, so a
pinned identity presents identically from either language.
Installation
npm install @camoufox/camoufox playwright-core
# then download the browser
npx camoufox fetch
playwright-core is a peer dependency — bring your own version (<1.63,
the same ceiling as the Python package). Node 22.15 or newer is required.
Usage
import { Camoufox } from "@camoufox/camoufox";
const browser = await Camoufox({
// any Camoufox option, plus any Playwright Firefox launch option
headless: true,
os: "windows",
geoip: true,
});
const page = await browser.newPage(); // a Playwright Page
await page.goto("https://example.com");
await browser.close();
Persistent profiles
const context = await Camoufox({ user_data_dir: "./profiles/alice" });
const page = await context.newPage();
Per-context identities
NewContext() gives each context its own fingerprint — real preset or
fpgen-synthesised — with its own audio noise seed. The
values are applied through addInitScript, so the setters self-destruct before
any page script runs.
import { Camoufox, NewContext } from "@camoufox/camoufox";
const browser = await Camoufox({ headless: true });
const context = await NewContext(browser, {
os: "macos",
proxy: { server: "http://proxy:8080", username: "u", password: "p" },
});
When a proxy is given and no webrtc_ip/timezoneId is, both are resolved
from the proxy's exit IP. If that lookup fails, NewContext() throws
InvalidIP rather than open a context that would show the host's values.
Server mode
import { launchServer } from "@camoufox/camoufox";
const server = await launchServer({ headless: true, port: 9222 });
console.log(server.wsEndpoint());
Persistent contexts are not servable — Playwright's launchServer can only
expose a pre-launched Browser.
Building launch options yourself
import { launchOptions } from "@camoufox/camoufox";
import { firefox } from "playwright-core";
const browser = await firefox.launch(await launchOptions({ os: "linux" }));
Options
Every option from the Python launch_options() is supported, with the same
snake_case names: os, config, block_images, block_webrtc,
block_webgl, disable_coop, webgl_config, geoip, geoip_db, humanize,
locale, addons, fonts, custom_fonts_only, exclude_addons, screen,
window, fingerprint, fingerprint_preset, ff_version, headless,
main_world_eval, allow_addon_new_tab, executable_path, browser,
firefox_user_prefs, proxy, enable_cache, args, env,
i_know_what_im_doing, debug, virtual_display, pin_cpu_cores. Anything else is passed
straight through to Playwright.
The returned launch options use Playwright's camelCase keys
(executablePath, firefoxUserPrefs) rather than Python's snake_case. As in
Python, headless: "virtual" is handled by Camoufox(), NewBrowser() and
launchServer(), not by launchOptions().
CLI
camoufox sync # refresh the version catalogue
camoufox fetch [version] # install the paired (or chosen) build, or a specific version
camoufox set [specifier] # pin a version or channel; no specifier opens a picker
camoufox set --release # go back to the build this release is paired with
camoufox set --geoip # pick a GeoIP source
camoufox list [installed|all] # list versions
camoufox remove [version] # remove one version, or everything (--select to pick)
camoufox active # print the active version
camoufox path # print the install directory
camoufox version # version / storage info
camoufox test [url] # open the Playwright inspector
camoufox server # launch a Playwright server
Each release of this package is paired with the one browser build it was built
and tested with, the same build as the camoufox Python release of the same
version. The first launch installs that build, and every launch uses it, until
you choose another with camoufox set. A launch then warns that the build
differs from the paired one. See the Python package's README, under "Which
browser build is used".
The commands and pickers match the Python CLI. The one exception is gui, a
PySide6 desktop app that only the Python package provides.
Development
The tests need a Python with pythonlib next to them, at the repo root (or
point CAMOUFOX_PYTHON at one):
python3.14 -m venv .venv # repo root
.venv/bin/pip install -r ci/requirements.txt -e pythonlib
.venv/bin/python scripts/pin-fpgen-model.py
cd typescript
pnpm install
pnpm build # tsc -> dist/, then copy pythonlib's data files into dist/data-files
pnpm test # records the golden fixtures from pythonlib, then vitest
pnpm check # biome lint + format
pnpm typecheck # tsc --noEmit
The data files (presets, fonts, voices, territoryInfo.xml, ...) are read from
pythonlib/camoufox/, the only copy in the repo; DATA_FILES in
src/paths.ts lists them, and the build copies them into the package.
Parity with pythonlib
The golden tests are what keep the two launchers twins. Before the suite runs,
tests/golden-setup.ts runs the scripts under scripts/golden/, which put the
Python code through hundreds of fixed inputs and record its output in
tests/fixtures/ (git-ignored); the TS tests must reproduce it exactly --
launch_options() byte for byte, including the CAMOU_CONFIG blob. A
pythonlib change that is not mirrored here fails pnpm test. Use Python 3.14:
pySum() follows its sum(), and on 3.12/3.13 that one test skips.
The end-to-end suite launches a real browser through both launchers and compares what a page sees:
CAMOUFOX_E2E=1 CAMOUFOX_EXECUTABLE=/path/to/camoufox-bin pnpm test tests/e2e.test.ts
Releasing
The npm package and pythonlib are released together by
.github/workflows/release.yml: a
prerelease under the next dist-tag for every tested merge to main that
changes the package, and a stable release under latest for a vX.Y.Z tag (see
ci/README.md). One job builds both packages and
runs scripts/check-pack.mjs (the version must equal pythonlib's; every data
file must be in the tarball; the tarball must install and import in an empty
project); PyPI gets its upload first, then npm gets that same tarball, published
with trusted publishing (no token is stored).
Licence
MIT, like the Python package (LICENSE); the browser itself is
MPL-2.0. The package contains ports of fpgen (Apache-2.0), CPython's random
and NumPy's pairwise summation; their notices are in
THIRD_PARTY_NOTICES.md, which ships with it.