Skip to content
michidkPublic

About

Native two-browser WebRTC connectivity and ICE path diagnostics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

icecheck — WebRTC path debugger

icecheck is a developer tool for testing whether two browsers can establish a direct WebRTC connection. It gathers ICE candidates, opens a data channel, sends synthetic video, and exposes the selected candidate pair and connection statistics.

icecheck manual WebRTC diagnostic

The offer and answer are exchanged by copy and paste or the browser's native share sheet. There are no rooms, WebSockets, or process-local connection registries, so the application is compatible with stateless deployments such as Vercel Functions.

What it tests

  • Native RTCPeerConnection negotiation between two browsers
  • LAN-only ICE with no configured ICE server
  • STUN-assisted ICE using configured public STUN endpoints
  • Host, peer-reflexive, and server-reflexive candidate discovery
  • Selected candidate pair and round-trip time
  • Ordered data-channel delivery with application-level pings
  • Synthetic-video negotiation and received RTP statistics

No TURN server is configured, so icecheck does not test relayed connectivity or bandwidth.

Run locally

Requirements: Node.js 24 and npm.

npm install
npm run dev

For a production build:

npm run build
npm start

The development server defaults to http://localhost:4173. Open the root page in two browsers to begin the diagnostic.

Diagnostic workflow

  1. On browser A, select an ICE strategy and choose Create offer.
  2. Copy or share A's outbound payload, paste it into Offer or answer to apply on browser B, and choose Apply inbound payload.
  3. Copy or share B's generated answer back to browser A and apply it.
  4. Read the verdict, then inspect connection state, candidate counts, selected pair, data-channel state, video state, and the JSON report on both browsers.
  5. Use Start over before starting another exchange.

Each base64url payload contains a complete session description. icecheck waits for non-trickle ICE gathering before encoding it, so no persistent signaling channel is needed. Payloads are versioned JSON envelopes containing the role, ICE strategy, SDP type, and SDP body; malformed, mismatched, oversized, and replayed payloads are rejected.

The negotiation sequence is deliberately small: the offerer creates a complete offer, the answerer validates and applies it before creating a complete answer, and the offerer validates and applies that answer. A reset or unmount closes peer connections, tracks, channels, timers, listeners, and pending work before another exchange begins.

Configuration

Variable Default Purpose
STUN_URLS stun:main.lohr.dev:3478,stun:stun.l.google.com:19302 Comma-separated STUN URLs returned by /config
HOST 0.0.0.0 Development-server bind address
PORT runtime-defined Development or production port
ALLOWED_HOSTS empty Comma-separated extra Vite development hosts

Only stun: and stuns: entries are accepted. TURN credentials are intentionally outside this tool's scope.

Deployment

Vercel is linked to the GitHub repository. Every push to main creates a production deployment and updates icecheck.vercel.app; other branches receive preview deployments.

The generated Nitro server exposes only stateless HTTP behavior:

  • the single-page diagnostic route /
  • GET /config for public ICE configuration
  • GET /health for readiness
  • static assets

There is no application-managed connection between the two browsers. A deployment can scale horizontally or restart without losing a room or socket because none exists. The copied SDP is never sent to the icecheck server.

HTTPS is recommended for consistent browser security behavior. For phone testing, use a URL reachable by both devices; localhost always refers to the device opening it.

Interpretation

Observation Likely meaning
LAN succeeds, STUN-assisted succeeds The browsers found a direct path; inspect selected_pair to see which candidate type won
LAN fails, STUN-assisted succeeds Server-reflexive discovery was needed
Both fail Candidate pairs were not mutually reachable, or local policy/firewall/browser support blocked negotiation
ICE connects, data channel fails Inspect SCTP/data-channel state and browser errors
Data works, video does not Inspect RTP stats, codec support, and generated-track support

Browser privacy protections may replace local addresses with mDNS names or redact details. Candidate types and connection states are more portable signals.

Project structure

server/
  diagnostic-runtime.ts       Shared /config and /health middleware
  middleware/                 Nitro-wide response security headers
  routes/                     Nitro HTTP adapters
src/
  components/                 App-wide presentation
  modules/icecheck/
    components/               Diagnostic UI
    hooks/                    Client lifecycle adapter
    lib/                      Manual codec and native WebRTC implementation
  routes/
    (home)/                   Single diagnostic page
test/
  browser/                    Browser lifecycle coverage
  contracts.test.mjs          Candidate-report contracts
  server.test.mjs             Built-server and codec integration coverage

The root route imports the feature's public React component. Browser-only WebRTC code is dynamically loaded by the feature hook and disposed whenever the route unmounts. See AGENTS.md for contributor guidance and module boundaries.

Verification

npm run verify
npm run test:e2e

verify runs ESLint, architecture checks, TypeScript, the production build, and Node integration tests.

Builds and npm run typecheck use TypeScript 7 through the typescript-compiler npm alias. The separate TypeScript 6 dependency supplies the compiler API required by typescript-eslint, which does not yet support TypeScript 7. The scripts select the compiler explicitly because both packages provide a tsc executable.

The browser suite starts the development server automatically and expects Chromium plus its system dependencies to be installed (npx playwright install --with-deps chromium). It performs a complete two-page offer/answer exchange in addition to lifecycle and failure-state checks. Set E2E_BASE_URL to run it against an existing deployment. Playwright output is kept under .playwright/.

Security

Base64url is encoding, not encryption. SDP can expose network addresses, temporary ICE credentials, DTLS fingerprints, codecs, and browser metadata. Transfer payloads only through a channel appropriate for the peers involved, and review them before posting in public issues.

The eventual WebRTC transport is encrypted by the browser, but icecheck does not authenticate the person who supplied a copied payload.

The Vite development adapter, Nitro server, and Vercel deployment all apply the same referrer, content-type, framing, and browser-permission restrictions.

License

MIT

About

Native two-browser WebRTC connectivity and ICE path diagnostics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages