Commit Graph
157 Commits
Author SHA1 Message Date
Pradeep Elankumaran f01ee38194 ci: fix reporter test (node:test vs Jest), bump CI timeout, disable bail
- Run reporter.test.js with node --test (uses node:test, not Jest)
- Exclude reporter.test from Jest run
- Bump client timeout to 60s in CI (Camoufox cold start on GitHub runners)
- Disable bail so full suite runs even on failure
2026-04-25 08:39:56 -07:00
Pradeep Elankumaran 5456b4d68e ci: add jest-junit for CI reporter 2026-04-25 08:35:35 -07:00
Pradeep Elankumaran dca08635de ci: run full test suite with browser in CI
- Drop --ignore-scripts so camoufox-js fetch runs
- Add playwright install --with-deps firefox for system libs
- Use xvfb-run for headless display on Ubuntu
- Only exclude live/ tests (external sites), run e2e/security/cookies/tabRecycling
- Node 20+24 matrix (drop 18, add 24 to match production)
- Remove unused jest-junit dep
2026-04-25 08:34:08 -07:00
Pradeep Elankumaran 4043d279e9 feat: anonymized crash/frustration reporter with per-tab health tracking
Adds opt-out crash reporter that files GitHub issues when sites cause
persistent problems. Only fires on frustration patterns (3+ consecutive
failures on the same tab), not individual errors.

Reporter (lib/reporter.js):
- Text anonymizer: strips paths, IPs, tokens, secrets, env vars, Fly IDs
- URL anonymizer: per-report salted HMAC for private domains, public infra
  domains (Cloudflare, Google, GitHub etc) preserved verbatim, paths stripped
  to depth-only (bullet points), query param count only, no content
- Per-tab health tracker via Playwright events: page crashes, JS errors,
  console error rate, request failures, dialog storms, redirect depth,
  HTTP 4xx/5xx histogram, frame count — all count-based, no content
- Rate limited (10/hr), deduped by stack signature, fire-and-forget
- GitHub PAT scoped to issues:write on single repo (swappable for forks)

Integration (server.js):
- Frustration detection in handleRouteError: tracks consecutiveFailures
  per tab, only reports at threshold=3 with full failure journal
- Health tracker attached to every new tab via createTabState()
- Event loop watchdog (5s stall threshold)
- Reports include: anonymized URL, health snapshot, failure journal

Config: opt-out (CAMOFOX_CRASH_REPORT_ENABLED!=false), requires PAT.
Tests: 69 passing (anonymization, URL privacy, dedup, rate limiting).
2026-04-25 08:34:08 -07:00
Pradeep Elankumaran bf32a6f688 Merge pull request #74 from hnshah/ci/add-github-actions-workflow
ci: add GitHub Actions workflow for unit tests (fixes #73)
2026-04-25 08:32:13 -07:00
Hiten Shah a403f54819 ci: add GitHub Actions workflow for unit tests
Adds CI status checks for PRs targeting master:

- Runs on Node 18 and 20 (matrix)
- Installs deps with npm ci (skipping postinstall browser fetch)
- Installs jest-junit for CI test reporting
- Runs pure unit tests (excludes server-dependent tests that need
  a running camoufox browser instance: cookies, security, tabRecycling)
- Removes .github/ from .gitignore to allow workflow tracking

Fixes #73
2026-04-23 16:11:30 -07:00
Pradeep Elankumaran da53f4b122 docs: clarify Docker requires make up, not docker build directly 2026-04-18 20:47:57 -07:00
Pradeep Elankumaran 6e5926e2ff Merge pull request #67 from jo-inc/next
feat: plugin system, session persistence, VNC interactive login
v1.6.0
2026-04-18 20:22:18 -07:00
Pradeep Elankumaran 49204fbba6 chore: bump version to 1.6.0 2026-04-18 20:21:24 -07:00
Pradeep ElankumaranandEdilson Osorio Jr 50bf37f83d docs: add per-plugin AGENTS.md with contributor credits, update README
Co-authored-by: Edilson Osorio Jr <3277320+eddieoz@users.noreply.github.com>
2026-04-18 18:06:26 -07:00
Pradeep Elankumaran 981e20be2b feat: enable persistence by default, profiles at ~/.camofox/profiles 2026-04-18 18:02:44 -07:00
Pradeep Elankumaran 835a6b5183 chore: remove upgrading section, no breaking changes for existing users 2026-04-18 17:58:25 -07:00
Pradeep Elankumaran fc63ed84ec chore: remove MIGRATION.md, upgrade is seamless for existing users 2026-04-18 17:58:08 -07:00
Pradeep Elankumaran 07dafa24d0 fix: youtube auth off by default to match pre-plugin behavior 2026-04-18 17:47:39 -07:00
Pradeep Elankumaran 13be5248cf test: add auth/plugins tests, fix fragile type tests, fix null userId in events 2026-04-18 17:46:48 -07:00
Pradeep Elankumaran 5107abc732 fix: split VNC plugin for scanner compliance, default noVNC to localhost 2026-04-18 17:39:44 -07:00
Pradeep Elankumaran bd7a7fb37f docs: add migration notes for breaking changes, make youtube auth configurable 2026-04-18 17:39:40 -07:00
Pradeep Elankumaran 3c630a67ff fix: plugin dep install respects object config, fix with-plugins stage
- install-plugin-deps.sh: handle both array and object plugin config formats
  Object format: only install deps for plugins where enabled !== false
- Dockerfile with-plugins stage: add missing COPY for plugins/ and config
- Sync package-lock.json version to 1.5.2
2026-04-18 17:39:02 -07:00
Pradeep Elankumaran 5740825921 fix: await async plugin hooks for mutating events 2026-04-18 17:38:29 -07:00
Pradeep ElankumaranandLeone Parise 0ac2490f29 feat: VNC plugin for interactive browser login + storage_state export
VNC plugin (plugins/vnc/) exposes Camoufox's virtual display via noVNC,
enabling interactive login for sites with fingerprint-based session
validation, CAPTCHAs, or MFA prompts.

- Plugin subclasses VirtualDisplay to override Xvfb resolution (default
  1920x1080, configurable via plugins.vnc.resolution or VNC_RESOLUTION)
- vnc-watcher.sh detects Camoufox's dynamically-assigned Xvfb display,
  attaches x11vnc, and proxies via noVNC on port 6080
- Registers GET /sessions/:userId/storage_state to export Playwright
  storageState (cookies + localStorage) after interactive login
- Emits session:storage:export event for persistence plugin integration
- System deps declared in apt.txt, installed via install-plugin-deps.sh

Plugin system changes:
- ctx passed by reference (not spread) so plugins can mutate factories
  like ctx.createVirtualDisplay
- pluginConfig moved to 3rd arg of register(app, ctx, pluginConfig)
- server.js exposes createVirtualDisplay factory + VirtualDisplay class
  on pluginCtx

Dockerfile: fix build for local Makefile (bind mounts from dist/),
name first stage for multi-stage support.

Co-authored-by: Leone Parise <leone.parise@gmail.com>
2026-04-18 17:21:50 -07:00
Pradeep Elankumaranandcompany8 040fe2aef7 feat: persistence plugin + per-plugin config support
Persistence as a plugin (not core) — camofox stays stateless by default.
Plugin hooks into session:creating, session:created, session:cookies:import,
session:destroyed, and server:shutdown lifecycle events to save/restore
Playwright storageState.

Plugin system now supports per-plugin config objects in camofox.config.json:
  { "plugins": { "youtube": { "enabled": true }, "persistence": { ... } } }
Array format still supported for backward compatibility.

Plugin config is passed to register() as ctx.pluginConfig.

Co-authored-by: company8 <compan@post.com>
2026-04-18 15:37:58 -07:00
Pradeep Elankumaranandcompany8 84a9d378f0 refactor: centralize session teardown with closeSession/closeAllSessions
Consolidate 7+ copy-pasted session teardown blocks into closeSession() and
closeAllSessions() helpers. Wrap getSession() in coalesceInflight to prevent
concurrent session creation races. All teardown paths now go through one
function that handles: context close, map cleanup, tab lock drain, download
cleanup, metrics refresh, and plugin event emission.

Based on the structural refactor from PR #62.

Co-authored-by: company8 <compan@post.com>
2026-04-18 15:31:07 -07:00
company8 20924cfa4c feat: add persistence helpers, inflight coalescing, bootstrap cookies
Standalone library modules from PR #62:
- lib/persistence.js: atomic storageState save/restore with SHA256-hashed user dirs
- lib/inflight.js: coalesceInflight primitive for concurrent session creation dedup
- lib/cookies.js: importBootstrapCookies helper for first-run cookie seeding
- Unit tests for all three modules

Co-authored-by: company8 <compan@post.com>
2026-04-18 15:26:39 -07:00
Pradeep Elankumaran d2f2e35248 config-based plugin loading, createMetric, colocated tests
- lib/plugins.js: read camofox.config.json plugins[] allow-list, skip
  unlisted plugins with debug log
- lib/metrics.js: export createMetric(type, opts) for plugin custom
  metrics (no-op stub when Prometheus disabled)
- server.js: expose createMetric + metricsRegistry on pluginCtx
- scripts/install-plugin-deps.sh: read config for plugin list, run
  post-install.sh hooks per listed plugin
- Dockerfile: COPY camofox.config.json into image
- openclaw.plugin.json: remove defaultPlugins (camofox.config.json
  is the source of truth)
- Move youtube test into plugins/youtube/youtube.test.js
2026-04-18 15:07:18 -07:00
Pradeep Elankumaran 27dfc5146b add plugin manager CLI: install, remove, list
npm run plugin install <git-url|local-path>
npm run plugin remove <name>
npm run plugin list

Supports git repos (root plugin or plugins/ subdirs) and local
directories. Auto-updates camofox.config.json, installs npm deps,
warns about system deps needing Docker rebuild.
2026-04-18 14:57:58 -07:00
Pradeep Elankumaran 9a8b719ff2 Make youtube a default plugin with post-install hooks
- Add defaultPlugins: ["youtube"] to openclaw.plugin.json
- Add plugins/youtube/post-install.sh for yt-dlp binary download
- Run install-plugin-deps.sh in base Dockerfile stage (not just with-plugins)
- install-plugin-deps.sh now runs post-install.sh hooks after apt packages
- with-plugins stage is now for rebuilding after adding third-party plugins
2026-04-18 10:47:01 -07:00
Pradeep Elankumaran 7eb1f9c354 Extract YouTube transcript into plugins/youtube/
- Move lib/youtube.js → plugins/youtube/youtube.js
- Create plugins/youtube/index.js with register(app, ctx)
- Create plugins/youtube/apt.txt (python3-minimal)
- Remove yt-dlp + python3 from base Dockerfile, add to with-plugins stage
- Wire plugin system into server.js: imports, pluginEvents, auth middleware,
  expanded pluginCtx (withUserLimit, safePageClose, normalizeUserId,
  validateUrl, safeError, buildProxyUrl, proxyPool, failuresTotal),
  28 event emissions across all route handlers
- Update test import path
2026-04-18 10:43:55 -07:00
Pradeep Elankumaran 094458fc60 Add plugin system: loader, event bus, shared auth, download events
- lib/plugins.js: plugin loader + EventEmitter factory (29 lifecycle events)
- lib/auth.js: extracted shared auth middleware (timingSafeCompare, isLoopbackAddress, requireAuth)
- lib/downloads.js: accept pluginEvents param, emit tab:download:start/complete
- scripts/install-plugin-deps.sh: reads plugins/*/apt.txt and installs via apt-get
2026-04-18 10:43:48 -07:00
Pradeep Elankumaran 435d03eca9 test: add unit tests for /type keyboard mode (#66) 2026-04-18 09:53:23 -07:00
Jeffrey Cruz e7546d7563 fix(Dockerfile): multi-arch build — download camoufox + yt-dlp at build time (#51) 2026-04-18 09:28:57 -07:00
Pradeep Elankumaran f2d2ce7230 Merge branch 'pr-66' into next 2026-04-18 09:19:36 -07:00
Pradeep Elankumaran c5cf01fc99 Merge branch 'pr-50' into next 2026-04-18 09:19:36 -07:00
Pradeep Elankumaran 457346fd4a Merge branch 'pr-63' into next 2026-04-18 09:19:36 -07:00
Pradeep Elankumaran c9c366c2a2 Merge branch 'pr-61' into next 2026-04-18 09:19:36 -07:00
Pradeep Elankumaran e05b62bf33 fix: race conditions in YT transcript + tab reaper session cleanup
Race 1 (YT transcript): browserTranscript cleanup counted tabGroups which
was always 0 (YT pages aren't registered in tabGroups). First concurrent
request to finish would close the context, killing other requests' pages.
Fix: use context.pages() to check actual live pages before closing.

Race 2 (tab reaper / session expiry): context.close() fired without await,
then session deleted from map. A request that already got the session ref
(between getSession return and newPage) would use a closing context.
Fix: set session._closing = true before teardown; getSession() treats
_closing sessions as dead and creates a new one.

Both fixes also applied to the session expiry timer (same pattern).
16 unit tests added covering _closing flag, getSession skip, YT concurrent
cleanup, and session expiry sentinel.
2026-04-18 09:13:09 -07:00
Pradeep Elankumaran 4a5a696514 feat: sync 3 fixes from jo-browser — navigate abort, snapshotBytes metric, test
- Navigate abort on tab delete: AbortController on tabState cancels
  in-flight page.goto when DELETE /tabs/:tabId fires, preventing
  hung navigations on deleted tabs
- snapshotBytes histogram (camofox_snapshot_bytes): tracks accessibility
  tree snapshot sizes by type (google_serp, full) at 4 observation points
- navigateAbort.test.js: 6 unit tests covering the abort lifecycle
2026-04-18 08:53:45 -07:00
Leone Parise 1cb5b25604 feat: unified /type endpoint with keyboard mode
Extends POST /tabs/:tabId/type (and the openclaw /act type kind) with new
optional params:

  mode: 'fill' | 'keyboard'  default 'fill'
  delay: number              ms per character in keyboard mode, default 30
  submit: boolean            press Enter after typing, default false
  pressEnter: boolean        alias for submit

mode='fill' (default) preserves existing behavior: locator.fill() or
page.fill(). mode='keyboard' focuses the target (if ref/selector is given)
and types char-by-char via page.keyboard.type(text, { delay }), producing
real key events.

Why: Ember/React contenteditable forms (LinkedIn's DM compose is the canonical
example) track input provenance via beforeinput/InputEvent and refuse to
recognize DOM-mutation writes like locator.fill() or execCommand('insertText').
Only OS-level key events from char-by-char typing satisfy them. Without this,
text appears in the editor but Send buttons never materialize and synthetic
Enter is ignored.

In keyboard mode, ref/selector is optional — if omitted the endpoint types
into whatever currently has focus. The submit flag adds Enter after typing in
a single locked operation (saves a roundtrip for click-to-send flows).

No behavior change for callers not passing mode.
2026-04-17 21:41:33 +00:00
Matt Van Horn 868b2ffb7f fix: clean up orphaned camoufox temp files on startup
When the server is killed with SIGKILL or OOM, camoufox's internal
.fea5*.so and .5ef7*.node temp files survive in os.tmpdir() and
accumulate. SIGTERM/SIGINT already trigger gracefulShutdown ->
browser.close() which removes them; this adds a one-shot startup
sweep for files older than 5 minutes to reclaim what survived a
non-graceful exit.

The 5-minute age threshold prevents deleting temp files from a
concurrently-running instance.

Fixes #58
2026-04-15 07:51:35 -04:00
Lolipop 68228fee55 fix: restore CAMOFOX_PORT to 9377 2026-04-13 16:27:53 +08:00
Pradeep Elankumaran 0b077358e0 v1.5.2 v1.5.2 2026-04-06 14:58:51 -07:00
Pradeep Elankumaran 38b945b903 fix: screenshot Content-Type guard + empty session cleanup (#38)
Two fixes from the code audit in #38:

1. plugin.ts camofox_screenshot: check Content-Type header before
   base64-encoding response. If server returns JSON/text instead of
   image (e.g. error with 200 status), return as text error instead
   of crashing the client. Also reject responses < 100 bytes.

2. Tab reaper now cleans up sessions with zero tabs remaining.
   Previously, after the reaper closed all idle tabs in a session,
   the empty session (browser context ~50-100MB) would linger until
   the 10-minute session timeout. Now it's closed immediately and
   the browser idle shutdown is triggered if no sessions remain.
2026-04-06 14:53:38 -07:00
dependabot[bot] 7b9de52772 build(deps): bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the / directory: @isaacs/brace-expansion and [minimatch](https://github.com/isaacs/minimatch).


Updates `@isaacs/brace-expansion` from 5.0.0 to 5.0.1

Updates `minimatch` from 10.1.1 to 10.2.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.1.1...v10.2.5)

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.1.1...v10.2.5)

---
updated-dependencies:
- dependency-name: "@isaacs/brace-expansion"
  dependency-version: 5.0.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: minimatch
  dependency-version: 10.2.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 20:19:00 +00:00
Pradeep Elankumaran 7e0a7cf217 v1.5.1 v1.5.1 2026-04-06 12:51:23 -07:00
Pradeep Elankumaran 0c1878d03c fix: lazy-load prometheus, off by default — fixes OpenClaw install block (#49)
OpenClaw's security scanner flagged lib/metrics.js with env-harvesting
(critical) because the file contained both `process.env` (in a comment!)
and `'POST'` string literals (in actionFromReq). The scanner regex is
case-insensitive: /\bpost\b/i matches string constants.

Changes:
- Lazy-load prom-client via dynamic import, only when PROMETHEUS_ENABLED=1
- When disabled (default), all metrics are no-op stubs — zero overhead
- Extract actionFromReq/classifyError into lib/request-utils.js (has POST
  strings but no process.env — scanner-safe)
- Extract extractPageImages into lib/images.js (has browser-side fetch
  inside page.evaluate but no fs.readFile — scanner-safe)
- Remove all process.env references from metrics.js (including comments)
- /metrics endpoint returns 404 when prometheus is disabled
- Add prometheusEnabled flag to lib/config.js

Verified against OpenClaw's actual scanner (skill-scanner.ts):
- Old metrics.js: CRITICAL env-harvesting finding → install blocked
- New split: 0 findings across all files → clean install
- Tested with `openclaw plugins install` on local OpenClaw build
2026-04-06 12:50:28 -07:00
Pradeep Elankumaran 0ca91b0fd9 docs: update README for v1.5.0 — proxy provider abstraction, tab recycling, new env vars v1.5.0 2026-04-05 20:41:09 -07:00
Pradeep Elankumaran a3e64d3e29 fix: restore default port to 9377 (was accidentally changed to 3000 during sync) 2026-04-05 20:22:29 -07:00
Minh Ha facee5e353 feat: Makefile for local Docker builds + bump deps
Adds a Makefile (from PR #43 by @mihado) that downloads camoufox and yt-dlp
outside the Docker build step for faster rebuilds. Auto-detects CPU architecture.

Also bumps transitive deps: brace-expansion 1.1.13, qs 6.14.2, picomatch 2.3.2,
path-to-regexp 0.1.13 (closes #44, #45, #46, #47, #48).
2026-04-05 20:15:12 -07:00
Pradeep Elankumaran 2c311154fe Merge pull request #42 from jo-inc/sync/jo-browser-apr5
v1.5.0: proxy provider abstraction, Google bot detection, WebGL, tab recycling
2026-04-05 20:14:57 -07:00
Tyler Vo 0a0d550091 fix: cherry-pick 5 community fixes from PR #39
- buildRefs timer leak: clearTimeout on resolve/reject paths
- DELETE /tabs, /tabs/group: accept userId from query OR body
- Scroll: support left/right horizontal scroll
- YouTube transcript: clean up phantom __yt_transcript__ session
- Plugin: kill orphan server process on startup failure

Cherry-picked from imtylervo's audit (PR #39). Excluded Claude Code-specific
changes (screenshot caps, timeout caps, includeScreenshot default, PROXY_PROTOCOL)
and the nth tracking fix (already present in our codebase).
2026-04-05 20:14:55 -07:00
Pradeep Elankumaran 8838c22b71 v1.5.0 2026-04-05 17:55:11 -07:00