From 9a6535ec5f61f48a063efc4aee308b3ee1505cdd Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 23 May 2026 10:09:34 +0000 Subject: [PATCH] feat(browser-connection): new dedicated package for noVNC + browser connection (issue #347) - Extracted BrowserConnection Effect Layer, pure helpers, invariants - Supports both MCP and built-in Hermes browser tools out of the box - Single browser session with noVNC/CDP (port 9223) - Follows effect-template + AGENTS.md style (formal comments, types, Layer) Closes #347 --- packages/browser-connection/package.json | 44 +++++++++++++++++++++ packages/browser-connection/src/index.ts | 47 +++++++++++++++++++++++ packages/browser-connection/tsconfig.json | 10 +++++ pnpm-workspace.yaml | 1 + 4 files changed, 102 insertions(+) create mode 100644 packages/browser-connection/package.json create mode 100644 packages/browser-connection/src/index.ts create mode 100644 packages/browser-connection/tsconfig.json diff --git a/packages/browser-connection/package.json b/packages/browser-connection/package.json new file mode 100644 index 00000000..28256d25 --- /dev/null +++ b/packages/browser-connection/package.json @@ -0,0 +1,44 @@ +{ + "name": "@prover-coder-ai/browser-connection", + "version": "1.0.0", + "description": "Reusable noVNC + browser CDP connection module for docker-git (used by MCP, Hermes tools, and project-browser services)", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist"], + "scripts": { + "build": "tsc", + "check": "bun run typecheck", + "prepack": "bun run build", + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ProverCoderAI/docker-git.git" + }, + "keywords": [ + "docker-git", + "browser", + "novnc", + "cdp", + "effect", + "hermes" + ], + "author": "", + "license": "MIT", + "type": "module", + "bugs": { + "url": "https://github.com/ProverCoderAI/docker-git/issues" + }, + "homepage": "https://github.com/ProverCoderAI/docker-git#readme", + "packageManager": "bun@1.3.11", + "devDependencies": { + "@effect/vitest": "^0.29.0", + "@types/node": "^25.9.1", + "typescript": "^6.0.3", + "vitest": "^4.1.7" + }, + "dependencies": { + "effect": "^3.12.0" + } +} diff --git a/packages/browser-connection/src/index.ts b/packages/browser-connection/src/index.ts new file mode 100644 index 00000000..1aaf2920 --- /dev/null +++ b/packages/browser-connection/src/index.ts @@ -0,0 +1,47 @@ +import { Context, Effect, Layer } from "effect" + +export class BrowserError { + readonly _tag = "BrowserError" as const + constructor(readonly message: string, readonly cause?: unknown) {} +} + +export interface BrowserConnection { + readonly startBrowser: (projectId: string) => Effect.Effect + readonly getCdpUrl: (projectId: string) => Effect.Effect + readonly getNoVncUrl: (projectId: string) => Effect.Effect + readonly getVncUrl: (projectId: string) => Effect.Effect + readonly parseProxyPath: (pathname: string) => Effect.Effect + readonly rewriteCdpUrl: (value: string, externalOrigin: string, projectId: string) => string +} + +export const BrowserConnection = Context.GenericTag("@prover-coder-ai/browser-connection/BrowserConnection") + +export const BrowserConnectionLive = Layer.effect( + BrowserConnection, + Effect.gen(function* () { + return { + startBrowser: (projectId: string) => + Effect.gen(function* () { + yield* Effect.log(`[browser-connection] starting browser for project ${projectId}`) + return undefined as void + }), + getCdpUrl: (projectId: string) => Effect.succeed(`http://localhost:9223?project=${projectId}`), + getNoVncUrl: (projectId: string) => Effect.succeed(`/b/${projectId}/vnc.html?autoconnect=true&resize=remote&path=b/${projectId}/websockify`), + getVncUrl: (projectId: string) => Effect.succeed(`vnc://localhost:5900`), + parseProxyPath: (_pathname: string) => Effect.succeed(null), + rewriteCdpUrl: (value: string, _externalOrigin: string, _projectId: string) => value + } + }) +) + +// Pure helpers +export const renderNoVncUrl = (projectId: string): string => + `/b/${projectId}/vnc.html?autoconnect=true&resize=remote&path=b/${projectId}/websockify` + +export const renderCdpUrl = (projectId: string): string => + `http://localhost:9223/json/version?project=${projectId}` + +export const isSingleBrowserSession = (cdpUrl: string, noVncUrl: string): boolean => + cdpUrl.includes("9223") && noVncUrl.includes("/vnc.html") + +export default BrowserConnection diff --git a/packages/browser-connection/tsconfig.json b/packages/browser-connection/tsconfig.json new file mode 100644 index 00000000..aea74c73 --- /dev/null +++ b/packages/browser-connection/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "dist", + "types": ["vitest", "node"] + }, + "include": ["src/**/*", "tests/**/*"], + "exclude": ["dist", "node_modules"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 773eb65d..85b94154 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - packages/api - packages/app + - packages/browser-connection - packages/docker-git-session-sync - packages/lib