Skip to content

Architecture overview

ProxyPro splits its work across two processes joined by a Unix domain socket. This page describes the shape of the system so you can read the source without getting lost.

Process model

┌───────────────────────────────────────────────────┐ ┌────────────────────────────┐
│ Electron App │ │ proxypro-engine (Go) │
│ │ │ │
│ ┌─────────────┐ contextBridge ┌──────────┐│gRPC│ │
│ │ Renderer │◀──ipcRenderer──────▶│ Main │◀┼─UDS─▶ HTTP MITM proxy on 9090 │
│ │ React+Vite │ (typed) │ Node ││ │ capture stream + bodies │
│ │ shadcn/ui │ │ grpc-js ││ │ rules engine │
│ └─────────────┘ └──────────┘│ │ cert + mobileconfig │
└───────────────────────────────────────────────────┘ └────────────────────────────┘
▼ HTTP proxy :9090
browsers / apps / devices

Engine (Go)

  • Single static binary at engine/bin/proxypro-engine (universal arm64+amd64).
  • Spawned and supervised by Electron’s main process.
  • Two listeners:
    • Unix socket — gRPC control plane (/tmp/proxypro-<pid>.sock)
    • TCP :9090 — HTTP proxy that browsers + apps configure.

Electron main (Node.js)

  • Owns the engine subprocess lifecycle (spawn, restart, graceful shutdown).
  • Hosts the @grpc/grpc-js client that talks to the engine.
  • Exposes a narrow, typed IPC surface (window.api.*) to the renderer via contextBridge. Renderer cannot touch raw ipcRenderer.

Electron renderer (React)

  • Pure React 18 + TanStack Table + Tailwind. No grpc, no Node APIs.
  • Subscribes to the capture stream through the main process.
  • Renders the capture list (virtualized), detail tabs, rules editor, and mobile setup UI.

Why two processes?

We could put everything in the Electron main process via Node addons or re-implementing the MITM in TypeScript. We split because:

ConcernWon by
Performance under load (10k flows/sec, gzip decode)Go
Cross-compile to a single static binaryGo
Mature TLS / HTTP/2 stackGo (crypto/tls, golang.org/x/net/http2)
Quick UI iterationReact + Vite HMR
Massive component ecosystemnpm
Sandboxed by default (contextIsolation)Electron

Keeping them separate also means the engine can ship headless someday — a CLI consumer of the same gRPC API.

IPC contracts

Two layers:

  1. Engine ⇄ main — gRPC, schema in proto/engine.proto. Service: Engine with RPCs Ping, GetVersion, StreamCaptures, GetRootCA, GetBody, UpdateRules, ListRules, GetLANInfo, GetMobileConfig.
  2. Main ⇄ renderer — Electron IPC, typed channels declared in app/src/shared/ipc-channels.ts.

The renderer never speaks gRPC. The main process bridges the two layers, batching capture events at 30fps before pushing to the renderer so the UI applies at most one re-render per frame.

Capture model

A request through the proxy emits a chain of FlowEvent records:

STARTED → REQUEST_HEADERS → REQUEST_BODY →
RESPONSE_HEADERS → RESPONSE_BODY → COMPLETED

Each event references the same flow_id. The renderer collapses them into a single FlowSummary per flow, upserting fields as events arrive.

The engine keeps a 10k-event in-memory ring buffer + a 100MB LRU for oversized request/response bodies. Bodies ≤ 64KiB ship inline in the event; larger ones live in the LRU and require an explicit GetBody fetch.

Cert + key management

On first run the engine:

  1. Generates an ECDSA P-256 root CA with 10-year validity.
  2. Persists root.pem + root.key.pem (mode 0600) under ~/Library/Application Support/ProxyPro/ca/.
  3. Mints per-SNI leaf certificates on demand, cached for 24h in memory.

The leaf cert advertises both h2 and http/1.1 in ALPN. After the client TLS handshake, the engine branches on the negotiated protocol and dispatches to either http.Server (h1) or http2.Server.ServeConn (h2).

Build artifacts

TargetOutput
make engineengine/bin/proxypro-engine (universal binary, ldflags-stamped version)
make appapp/out/{main,preload,renderer} (Vite production bundles)
make distapp/dist/ProxyPro.app + .dmg (ad-hoc signed via electron-builder)
make devbuilds engine + runs pnpm dev with HMR
make testruns Go unit + integration tests
pnpm --dir app testVitest renderer + main unit tests
pnpm --dir app test:e2ePlaywright Electron E2E

Further reading