How Croc Integrates the Browser WebAssembly Client: Architecture and Implementation
Croc integrates its browser WebAssembly client by compiling the full Go codebase into a Wasm binary that runs inside the browser, using WebRTC for peer-to-peer transport and WebSocket signaling through an embedded relay server.
Croc is a secure, peer-to-peer file transfer tool written in Go. The schollz/croc repository extends the command-line utility into web browsers through a WebAssembly client that reuses the exact same protocol implementation without code duplication, allowing users to send and receive files directly from a web interface while maintaining end-to-end encryption.
Architecture Overview
The integration relies on three tightly-coupled components working together to bridge the Go runtime with browser APIs.
The Wasm Bundle
The core croc engine is compiled to src/webassets/dist/croc.wasm using Go's WebAssembly target. This binary contains the complete protocol implementation including message handling, encryption, chunked transfer logic, and progress reporting. The build process uses the standard Go toolchain with specific build tags to target the js/wasm environment.
Browser Glue Code
The TypeScript client in web/src/wasm/client.ts handles loading the Wasm module and initializing the Go runtime provided by src/webassets/dist/wasm_exec.js. This glue code creates the bridge between JavaScript and Go, exposing a small API that the React UI uses to initiate transfers.
Web-Relay Server
The src/webrelay/webrelay.go package serves the static UI assets and Wasm files while exposing a WebSocket endpoint at /ws. This server runs the same croc core logic in "relay mode," forwarding signaling messages between peers without requiring a separate signaling infrastructure.
Build Process
The Go build pipeline compiles the entire codebase into a single Wasm binary using specific build constraints.
go build -o src/webassets/dist/croc.wasm -tags=js,wasm ./src/...
This command targets the js and wasm build tags, instructing the compiler to substitute platform-specific networking code with browser-compatible implementations. The resulting binary is placed in src/webassets/dist/ alongside wasm_exec.js, which provides the JavaScript runtime required by Go's syscall/js package.
Browser Initialization
When a user loads the web interface, the browser downloads and instantiates the Wasm module. The entry point in web/src/wasm/client.ts initializes the Go runtime and starts the croc program inside the browser.
// web/src/wasm/client.ts
import { Go } from "./wasm_exec.js";
export async function startCroc(): Promise<void> {
const go = new Go(); // JS wrapper that implements the Go runtime API
// Fetch and instantiate the compiled croc.wasm
const { instance } = await WebAssembly.instantiateStreaming(
fetch("/croc.wasm"),
go.importObject
);
// Run the Go program inside the browser
go.run(instance);
}
The WebAssembly.instantiateStreaming call fetches the binary and compiles it in parallel, while go.run() boots the Go program exactly as it would execute on the command line, but within the browser's JavaScript environment.
Transport Adaptation and Signaling
Inside the core code, the transport layer abstracts the underlying communication channel. When compiled for Wasm, the implementation in src/comm/comm.go swaps native TCP sockets for WebRTC data channels, enabling peer-to-peer connectivity without browser sandbox restrictions.
WebSocket Signaling
Since WebRTC requires out-of-band signaling to establish peer connections, the browser client opens a WebSocket connection to the relay server.
// Inside web/src/wasm/client.ts
declare global {
interface Window {
crocSignal: (msg: string) => void;
}
}
// Establish the signalling channel
const ws = new WebSocket(`${location.origin.replace(/^http/, "ws")}/ws`);
ws.onmessage = ev => window.crocSignal?.(ev.data);
ws.onopen = () => console.log("signalling channel ready");
ws.onerror = err => console.error("WS error:", err);
The webrelay server forwards signaling payloads—SDP offers, answers, and ICE candidates—between peers through the /ws endpoint. Once the WebRTC channel is established, the standard croc protocol (handshake, PAKE authentication, chunked encryption) runs over it identically to the CLI version.
Starting the Relay Server
To support browser clients, deploy the embedded web relay that serves both the UI and signaling infrastructure.
package main
import (
"context"
"github.com/schollz/croc/v10/src/webrelay"
)
func main() {
// Run the embedded web UI and signalling server on :8080
if err := webrelay.Run(context.Background(),
webrelay.Config{
Addr: ":8080", // HTTP & WS listener
EnableTLS: false, // can be true with certs
Debug: false,
}); err != nil {
panic(err)
}
}
The webrelay.Run function (called from cli.go in the standard distribution) embeds the UI files using Go's embed.FS and registers HTTP handlers for static assets and the WebSocket endpoint.
React UI Integration
The React frontend in web/src/App.tsx subscribes to events emitted by the Wasm client through the postMessage bridge provided by wasm_exec.js.
// web/src/App.tsx
import { useEffect, useState } from "react";
import { startCroc } from "./wasm/client";
export default function App() {
const [progress, setProgress] = useState(0);
useEffect(() => {
// Boot the Wasm client when the page loads
startCroc();
// Listen for progress events emitted from Wasm
window.addEventListener("crocProgress", (e: any) => {
setProgress(e.detail.percent);
});
}, []);
return (
<div>
<h1>croc – file transfer in the browser</h1>
<progress max={100} value={progress} />
</div>
);
}
The Wasm side forwards progress updates, errors, and completion events through the JavaScript boundary, allowing the React state to display real-time transfer status without polling.
Summary
- Unified Codebase: The WebAssembly client uses the exact same Go code as the CLI, compiled with
-tags=js,wasmtosrc/webassets/dist/croc.wasm, eliminating protocol drift between platforms. - WebRTC Transport: Instead of TCP sockets, the Wasm build uses WebRTC data channels for peer-to-peer connectivity, with signaling coordinated through the
/wsWebSocket endpoint insrc/webrelay/webrelay.go. - Runtime Bridge: The
wasm_exec.jsruntime andweb/src/wasm/client.tsglue code instantiate the Go program inside the browser and connect it to browser APIs for networking and UI events. - Embedded Deployment: The
webrelaypackage serves the React UI (web/src/App.tsx) and Wasm assets from a single binary using Go'sembed.FS, simplifying deployment to a single executable.
Frequently Asked Questions
How does croc compile its Go code for browser use?
Croc uses the standard Go compiler with WebAssembly target support. The build command go build -o src/webassets/dist/croc.wasm -tags=js,wasm ./src/... instructs the compiler to produce a Wasm binary while substituting platform-specific system calls with JavaScript-compatible implementations via the syscall/js package.
What transport does the croc WebAssembly client use instead of TCP?
The Wasm client uses WebRTC data channels for peer-to-peer transport. The transport abstraction in src/comm/comm.go detects the js/wasm build constraints and swaps the default TCP implementation for WebRTC, enabling direct browser-to-browser file transfers without plugins or native code.
How does the browser client connect to peers without native sockets?
The browser client connects to a WebSocket signaling server at /ws provided by src/webrelay/webrelay.go. This server exchanges SDP offers, answers, and ICE candidates between peers to establish the WebRTC connection, after which the encrypted data flows directly between browsers without server involvement.
Can the WebAssembly client communicate with the CLI version?
Yes. Because the Wasm binary contains the identical protocol implementation—including PAKE authentication, chunk encryption, and progress reporting—the browser client can send files to or receive files from the standard command-line croc tool. Both implementations speak the same wire protocol, differing only in the underlying transport layer (WebRTC vs TCP).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →