DEV Community

Upendra Dasanayaka
Upendra Dasanayaka

Posted on

How I Built a Zero-Install, Multi-Gigabyte P2P File Transfer Engine in the Browser with WebRTC & File System Access API

Ever tried sending a 15GB 4K video file or a raw dataset to a teammate sitting at the next desk?

The options are surprisingly clunky:

  1. Hunt for a physical USB flash drive.
  2. Upload it to Google Drive, Dropbox, or Slack (wasting your internet bandwidth, hitting upload limits, and waiting for compression) only for your coworker to spend another 20 minutes downloading it back over the same local network.

I wanted to fix this with a zero-friction experience: no desktop client installation, no cloud storage limits, and full local network transfer speed directly inside the browser.
That led me to build FluX — an open-source, peer-to-peer (P2P) file sharing tool running entirely over WebRTC and modern browser streaming APIs.

Here is an architectural deep dive into how FluX streams multi-gigabyte files directly between machines without crashing browser memory.

🏗️ 1. High-Level Architecture: How FluX Works FluX uses a hybrid model:

  1. Signaling Server (Node.js + Socket.io): Coordinates peer discovery and exchange of SDP (Session Description Protocol) offers, answers, and ICE candidates.
  2. Direct P2P Data Channel (WebRTC SCTP/DTLS): Once the handshake completes, all file bytes travel directly peer-to-peer over the local router/LAN, bypassing the server entirely. [ Sender Browser ] [ Receiver Browser ] | | |--- 1. SDP Offer & ICE Candidates (via Socket.io) ->| |<-- 2. SDP Answer & ICE Candidates (via Socket.io) -| | | |======= 3. Direct WebRTC Data Channel (LAN) =======>| | [SCTP Chunks -> File System Access API] |

⚡ 2. Solving the 3 Hardest Browser Engineering Challenges

Challenge A: Bypassing the Browser RAM Crash (Direct-to-Disk Streaming)

Traditional browser file downloads build an in-memory Blob or ArrayBuffer before triggering a download link. If you transfer a 10GB file, the browser tab consumes 10GB+ of RAM and instantly crashes with an Out of Memory (OOM) error.
The Solution: The File System Access API (showSaveFilePicker).
Instead of holding chunks in memory:

  1. The receiver selects a destination file handle on disk.
  2. A FileSystemWritableFileStream is created.
  3. Incoming binary chunks from the WebRTC Data Channel are piped directly into disk storage in real time.

javascript
// Request destination file handle from user
const fileHandle = await window.showSaveFilePicker({
suggestedName: incomingMetadata.name,
});
const writableStream = await fileHandle.createWritable();
// Write incoming WebRTC chunk directly to disk
dataChannel.onmessage = async (event) => {
if (event.data instanceof ArrayBuffer) {
await writableStream.write(event.data);
} else if (event.data === "TRANSFER_COMPLETE") {
await writableStream.close();
console.log("File saved directly to disk!");
}
};

Challenge B: Smart Backpressure Management (bufferedAmount)

WebRTC Data Channels have an internal buffer queue. If your disk or local network sends faster than the channel can flush, RTCDataChannel.bufferedAmount skyrockets, leading to packet drops or crashed tabs.

The Solution: Custom Backpressure Queue.

We monitor dataChannel.bufferedAmount and pause chunk slicing whenever the buffer exceeds a high-water mark (e.g., 8MB), resuming only when bufferedamountlow fires:

javascript

const CHUNK_SIZE = 64 * 1024; // 64 KB per chunk
const BUFFER_THRESHOLD = 8 * 1024 * 1024; // 8 MB high watermark
dataChannel.bufferedAmountLowThreshold = 1 * 1024 * 1024; // 1 MB low watermark
async function sendFile(file, dataChannel) {
let offset = 0;
while (offset < file.size) {
// Check if buffer is backed up
if (dataChannel.bufferedAmount > BUFFER_THRESHOLD) {
await new Promise((resolve) => {
dataChannel.onbufferedamountlow = () => {
dataChannel.onbufferedamountlow = null;
resolve();
};
});[](url)
}
const chunk = file.slice(offset, offset + CHUNK_SIZE);
const buffer = await chunk.arrayBuffer();
dataChannel.send(buffer);
offset += CHUNK_SIZE;
}
dataChannel.send("TRANSFER_COMPLETE");
}

Challenge C: Non-Blocking UI with Web Workers

Calculating cryptographic checksums (SHA-256) and slicing massive files can freeze the React render thread. We offloaded hashing and file chunking into dedicated Web Workers, keeping the Framer Motion animations smooth at 60 FPS.

🛠️ The Tech Stack
Frontend: React 18, Zustand (Atomic state management), Framer Motion, Tailwind CSS.
Protocols: WebRTC (SCTP/DTLS Data Channels), WebSockets (Socket.io).
Browser APIs: File System Access API, Web Workers, Blob slicing.
Signaling Backend: Node.js, Express, Socket.io (Stateless signaling).

🚀 Try It Out & Source Code
🌐 Live Demo: fluxbykingupe.vercel.app
💻 GitHub Repository: github.com/KING-UPE/FluX
👨‍💻 Portfolio & Contact: Upendra Dasanayaka

Have feedback or ideas on improving WebRTC throughput? Feel free to drop a comment or open an issue on GitHub!

Top comments (0)