Return to notes
2026.01.08

Clipboard Sync: Building a Peer-to-Peer Architecture

networkingreact-nativep2parchitecture
DATE
EST. READ
4 min read

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:

  1. Discovery — Finding devices on the local network via mDNS
  2. Connection — Establishing direct TCP connections between peers
  3. Transfer — Sending clipboard data (text, images, files) with progress tracking
plaintext
┌──────────────┐          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:

typescript
// 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:

typescript
// 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:

typescript
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:

typescript
// 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

  1. 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.

  2. Cross-platform networking is full of surprises. mDNS behaves differently on macOS, Windows, and Android. Firewalls, VPNs, and network isolation add more complexity.

  3. 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.

© 2026 Prince