openalice-browse
[Atlas card](https://atlas.blal.pro/repos/openalice-browse)
Architecture rating
Clean AST — 0 circular deps, 0 dead imports, 0 god modules, 0 naming violations, avg complexity 3.0 — and explicitly built as the sibling of openalice-pty following shared health/metrics/manifest conventions; minor drift (undeclared BROWSE_INTERNAL_SECRET, 1 format issue) keeps it below exemplary.
Valid present manifest with a clear summary, 7 enumerated exposed endpoints, 5 @feature annotations, AGENTS.md, README and a live Atlas card drive a strong docs score; weakened by 17 missing docstrings and a thin roadmap (Atlas roadmap 30/100).
GET /metrics Prometheus exposition and a working GET /health probe (matched, status 200) are present and live-verified; however 13 console statements suggest ad-hoc logging rather than structured logs, and no tracing signal — solid basics, gaps in log discipline and distributed tracing.
Manifest exposes auth-sensitive session/command HTTP+WS endpoints and Atlas flags an undeclared-but-used BROWSE_INTERNAL_SECRET implying shared-secret auth exists, but it's undocumented/drifting; a headless-browser command channel (navigate/click/type) is inherently SSRF-prone and no rate-limit, security-header, or input-guard signal is present, so authz depth is unverified — grading conservatively.
Four focused test files (session, session-unit, command-endpoint, registry) cover the core session lifecycle and command path with passing CI (2026-05-22), but it's a small count for a CDP/WebSocket screencast service — no evidenced streaming, SSRF, or failure-path regression tests, so critical-path coverage is partial.
Stateless service: manifest_owns_tables is empty and there are no migrations — sessions are in-memory (registry.test.js), so no schema/RLS/FK/GDPR surface to evaluate; scored neutral-conservative as not-applicable rather than well-engineered persistence.
Health endpoint live (200, 100% 24h uptime) and low complexity (avg 3, max 11) help, but 3 bare/broad excepts plus a long-lived Chromium/CDP+WebSocket service with no evidenced graceful-shutdown, timeout, retry, or session-reaping signal is a real risk for leaked browser processes; idempotency on session/command unverified.
source · auto-grader-2026-06-16
Ecosystem manifest
.atlas-deps.ymlHeadless Chromium browser service — Playwright CDP screencast over WebSocket, JSON command channel for click/type/navigate. Sibling of openalice-pty.
- via INTERNAL_SECRETAgent Desktop BrowserApp connects over WS to receive JPEG screencast frames and send click/type/navigate commands
- INTERNAL_SECRET
- BROWSE_PORT
- BROWSE_HEADLESS
- BROWSE_DEFAULT_URL
- BROWSE_PUBLIC_HOST
- BROWSE_PUBLIC_WS_SCHEME
- http_endpoint: POST /v1/sessions — create browser session
- http_endpoint: GET /v1/sessions — list sessions
- http_endpoint: DELETE /v1/sessions/:id — close session
- http_endpoint: POST /v1/sessions/:id/command — send command to session
- ws_endpoint: ws://.../v1/sessions/:id/stream — JPEG screencast stream
- http_endpoint: GET /health
- http_endpoint: GET /metrics — Prometheus exposition
Atlas grepped the repo's source for env reads (rust env::var · ts process.env · py os.environ · shell $VAR) and diffed against the manifest's consumes_env. Two lists below: stale declarations and undeclared reads.
- BROWSE_INTERNAL_SECRET
Code composition
tokei · loc- JavaScript93.4%
- YAML3.3%
- JSON1.8%
- Dockerfile1.4%
- JavaScript93.4%
- YAML3.3%
- JSON1.8%
- Dockerfile1.4%
- JavaScript93.4%· 1,264
- YAML3.3%· 45
- JSON1.8%· 25
- Dockerfile1.4%· 19
| Language | % code | code | comments | blanks | files |
|---|---|---|---|---|---|
| JavaScript | 93.4% | 1,264 | 274 | 165 | 7 |
| YAML | 3.3% | 45 | 25 | 3 | 2 |
| JSON | 1.8% | 25 | 0 | 0 | 1 |
| Dockerfile | 1.4% | 19 | 20 | 10 | 1 |
| Markdown | 0.0% | 0 | 91 | 41 | 2 |
Telemetry ribbon · last 90 days
2 eventsbrowse/api
2 features- browse.api.command-dispatchsrc/main.js:164Agent-side control endpoint — POST a single JSON command into an existing session. Body shape mirrors the WebSocket frames session.dispatch() already handles, so the openalice-live-control dispatcher (and any future server-side caller) can drive click / type / navigate / etc. without holding a WS open. 200 returns whatever session.dispatch produces (or `{ ok: true }` for fire-and-forget commands). 404 when session is missing, 400 when dispatch surfaces an `error` reply.
- browse.api.session-lifecyclesrc/registry.js:4Maintains the authoritative `Map<sessionId, Session>`. Exposes add / get / list / remove for the HTTP control plane. The idle sweep (`startSweep`) runs every 15 s and closes sessions whose last WebSocket client disconnected more than `idleTimeoutMs` ago (default 60 s) — preventing zombie Chromium tabs from accumulating. @feature browse.ops.observability Tracks `metrics.totalScreencastFrames`, `totalSessionsCreated`, and `totalSessionsClosed` in-process counters that `GET /metrics` exposes as Prometheus text. v0 is single-process: one Chromium browser instance, one Map of sessionId -> Session. Per-tenant browser pods come later; for now we rely on Playwright's BrowserContext for isolated cookies and local-storage between sessions inside the same Chromium process. Idle sweep: every 15s we look for sessions that have had no subscribed WS clients for `idleTimeoutMs` (default 60s) and tear them down. Keeps zombie tabs from piling up if a frontend window is closed without sending DELETE.
browse/core
2 features- browse.core.cdp-screencastsrc/main.js:4Boots a single shared headless Chromium via Playwright. Sessions use CDP `Page.startScreencast` to stream JPEG frames; each frame is forwarded as a binary WebSocket message to every subscribed client. @feature browse.api.session-lifecycle Express HTTP control plane: POST/GET/DELETE /v1/sessions manage the session lifecycle. GET /health and GET /metrics are public probes; all other routes require x-internal-secret or ?token= auth. Boots: - one shared headless Chromium via Playwright - an Express HTTP control plane (POST/GET/DELETE /v1/sessions, /health, /metrics) - a `ws` server bound to the SAME http.Server, only handling the /v1/sessions/:id/stream upgrade path Auth model — v0: Every HTTP entrypoint (except /health) requires `x-internal-secret: $INTERNAL_SECRET` OR `?token=$INTERNAL_SECRET` (WS upgrade only supports query-string tokens because browsers can't add custom headers to WebSocket handshakes). This service is intended to live behind Traefik on the internal `proxy` network and never be reached directly from the public internet. Process model — v0: Single Node process, single Chromium. Each session is its own BrowserContext (isolated cookies / localStorage). For real multi-tenant isolation, fan out to a per-tenant Docker pod and run one of these services in each.
- browse.core.cdp-screencastsrc/session.js:4Starts a CDP screencast (`Page.startScreencast`) on the session's Page. Each `Page.screencastFrame` event is decoded from base64 and broadcast as a binary WebSocket frame to every subscribed client. Frame acks (`Page.screencastFrameAck`) are sent immediately so CDP never stalls due to an unacknowledged backlog. @feature browse.api.command-dispatch `dispatch(message)` routes JSON command objects — click, mousemove, type, key, navigate, scroll, resize, screenshot, url — to the corresponding Playwright / CDP method. Both the WebSocket handler (main.js) and the HTTP command endpoint call this same method. Lifecycle: 1. constructor stashes options. 2. start() creates the BrowserContext from the shared Browser, opens a Page, navigates to the requested URL, attaches a CDPSession and starts the JPEG screencast. 3. attachClient(ws) subscribes a WS to receive binary frames. Multiple clients per session are allowed; useful for the "share my browser" review flow. 4. dispatch(message) accepts a single JSON command and routes it to the right Playwright/CDP method. 5. close() stops the screencast, closes the page, closes the context. Browser stays alive for the next session. Coordinate space: the screencast frames the frontend renders are `viewport.width x viewport.height` device-pixels. Click coords sent back to us MUST be in that same pixel space — the BrowserApp is responsible for mapping mouse events through the canvas bounding rectangle.
browse/ops
1 feature- browse.ops.observabilitysrc/main.js:100no description