Clipboard Sync: Building a Peer-to-Peer Architecture
The Vision
Imagine copying something on your phone and pasting it on your laptop — instantly, without cloud servers, accounts, or internet. That's Qlip: a peer-to-peer clipboard sync tool that works entirely over your local network.
Building it meant solving some genuinely hard problems in networking, data transfer, and cross-platform compatibility.
Architecture Overview
Qlip's architecture has three main layers:
- Discovery — Finding devices on the local network via mDNS
- Connection — Establishing direct TCP connections between peers
- Transfer — Sending clipboard data (text, images, files) with progress tracking
┌──────────────┐ mDNS ┌──────────────┐
│ Device A │◄──── Discovery ────►│ Device B │
│ (Desktop) │ │ (Mobile) │
│ │ TCP Connection │ │
│ Clipboard │◄═══════════════════►│ Clipboard │
│ Watcher │ Chunked Transfer │ Watcher │
└──────────────┘ └──────────────┘Device Discovery with mDNS
The first challenge is finding other Qlip devices on the network. I use mDNS (multicast DNS) — the same protocol that powers AirDrop and Chromecast discovery:
// Advertise this device on the network
const advertiser = mdns.createAdvertisement(
mdns.tcp("qlip"),
port,
{
name: deviceName,
txtRecord: {
deviceId: getDeviceId(),
platform: process.platform,
},
}
);
// Browse for other Qlip devices
const browser = mdns.createBrowser(mdns.tcp("qlip"));
browser.on("serviceUp", (service) => {
console.log("Found device:", service.name);
connectToPeer(service.addresses[0], service.port);
});The beauty of mDNS is that it works without any server infrastructure. Devices find each other automatically on the same network.
The Transfer Protocol
Sending clipboard content sounds simple until you need to handle:
- Large files (100MB+ videos, disk images)
- Progress tracking with accurate percentage
- Backpressure when the receiver can't keep up
- Connection drops mid-transfer
I designed a custom binary protocol with a header that describes the payload:
// Protocol header structure
interface QlipHeader {
version: number; // Protocol version
type: "text" | "image" | "file"; // Content type
totalSize: number; // Total payload size in bytes
chunkSize: number; // Size of each chunk
filename?: string; // Original filename (for files)
mimeType?: string; // MIME type
}
// Chunk structure
interface QlipChunk {
index: number; // Chunk sequence number
data: Buffer; // Chunk payload
isLast: boolean; // Whether this is the final chunk
}Handling Large File Transfers
The biggest challenge was transferring large files without freezing the UI or exhausting memory. The solution: streaming with backpressure.
Instead of reading the entire file into memory, I stream it in chunks:
class StreamingFileWriter {
private writeStream: fs.WriteStream;
private bytesWritten = 0;
async writeChunk(chunk: Buffer): Promise<void> {
const canContinue = this.writeStream.write(chunk);
if (!canContinue) {
// Backpressure! Wait for the stream to drain
await new Promise<void>((resolve) =>
this.writeStream.once("drain", resolve)
);
}
this.bytesWritten += chunk.length;
this.emitProgress();
}
}This approach keeps memory usage constant regardless of file size — whether you're sending a 1KB text snippet or a 1GB video file.
Connection Stability
Real-world networks are messy. Wi-Fi drops, devices sleep, and connections go stale. I implemented a heartbeat mechanism to detect and handle dead connections:
// Ping every 5 seconds
const heartbeat = setInterval(() => {
if (Date.now() - lastPong > 15000) {
// No pong in 15s — connection is dead
handleDisconnect();
return;
}
peer.send({ type: "ping" });
}, 5000);When a connection drops, Qlip automatically reconnects with exponential backoff — so you don't even notice the interruption.
Lessons Learned
-
TCP is harder than you think. Handling partial reads, message boundaries, and backpressure correctly took multiple iterations. The "happy path" is easy; the edge cases are where the real work lives.
-
Cross-platform networking is full of surprises. mDNS behaves differently on macOS, Windows, and Android. Firewalls, VPNs, and network isolation add more complexity.
-
Progress tracking matters more than speed. Users are far more patient with a transfer that shows clear progress than a fast transfer with no feedback.
What's Next
Qlip is still in active development. The roadmap includes:
- End-to-end encryption using a shared key derived from a pairing code
- Clipboard history with search and pinning
- Multi-device sync where a copy on any device appears everywhere
Follow the journey on GitHub or try Qlip at useqlip.com.