DEV Community

VaultKeepR
VaultKeepR

Posted on

Cross-Device Sync Without Cloud: P2P Alternative

The Cloud Dependency Problem

85% of password managers rely on cloud storage for cross-device synchronization. But what happens when AWS goes down, your internet cuts out, or you simply don't trust third parties with your encrypted data?

Traditional sync solutions create a single point of failure and force you to trust cloud providers with your most sensitive information. Even with client-side encryption, metadata leaks and server breaches remain constant threats.

Why Local-First Sync Matters Now

The push for data sovereignty is accelerating. European GDPR, growing privacy awareness, and recent cloud outages have developers seeking alternatives to centralized synchronization.

Local-first architecture isn't just about privacy—it's about resilience. Your data remains accessible when networks fail, servers go down, or services shut down entirely.

Technical Deep Dive: P2P Sync Protocols

WebRTC for Direct Device Communication

WebRTC enables direct peer-to-peer connections between devices on the same network or across the internet:

class P2PSync {
  private peerConnection: RTCPeerConnection;
  private dataChannel: RTCDataChannel;

  async initializeConnection(isInitiator: boolean) {
    this.peerConnection = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    });

    if (isInitiator) {
      this.dataChannel = this.peerConnection.createDataChannel('sync', {
        ordered: true
      });
      this.setupDataChannel();
    } else {
      this.peerConnection.ondatachannel = (event) => {
        this.dataChannel = event.channel;
        this.setupDataChannel();
      };
    }
  }

  private setupDataChannel() {
    this.dataChannel.onopen = () => {
      console.log('P2P sync channel opened');
      this.syncData();
    };

    this.dataChannel.onmessage = (event) => {
      this.handleSyncMessage(JSON.parse(event.data));
    };
  }

  async syncData() {
    const localData = await this.getLocalChanges();
    this.dataChannel.send(JSON.stringify({
      type: 'sync_request',
      changes: localData,
      timestamp: Date.now()
    }));
  }
}
Enter fullscreen mode Exit fullscreen mode

Conflict Resolution with Vector Clocks

Managing concurrent updates without a central authority requires sophisticated conflict resolution:

interface VectorClock {
  [deviceId: string]: number;
}

class ConflictResolver {
  private deviceId: string;
  private vectorClock: VectorClock;

  constructor(deviceId: string) {
    this.deviceId = deviceId;
    this.vectorClock = { [deviceId]: 0 };
  }

  createChange(data: any): Change {
    this.vectorClock[this.deviceId]++;

    return {
      id: crypto.randomUUID(),
      deviceId: this.deviceId,
      data,
      vectorClock: { ...this.vectorClock },
      timestamp: Date.now()
    };
  }

  resolveConflict(local: Change, remote: Change): Change {
    // Compare vector clocks to determine causality
    const localClock = local.vectorClock;
    const remoteClock = remote.vectorClock;

    let localAhead = false;
    let remoteAhead = false;

    for (const deviceId in { ...localClock, ...remoteClock }) {
      const localValue = localClock[deviceId] || 0;
      const remoteValue = remoteClock[deviceId] || 0;

      if (localValue > remoteValue) localAhead = true;
      if (remoteValue > localValue) remoteAhead = true;
    }

    if (localAhead && !remoteAhead) return local;
    if (remoteAhead && !localAhead) return remote;

    // Concurrent changes - use deterministic resolution
    return local.timestamp > remote.timestamp ? local : remote;
  }
}
Enter fullscreen mode Exit fullscreen mode

Local Network Discovery

Devices need to find each other without internet connectivity:

class LocalDiscovery {
  private mdnsService: any;
  private peers: Map<string, PeerInfo> = new Map();

  async startDiscovery() {
    // Broadcast service on local network
    this.mdnsService = mdns.createAdvertisement(mdns.tcp('vaultkeepr-sync'), 8080, {
      deviceId: this.getDeviceId(),
      version: '1.0.0'
    });

    this.mdnsService.start();

    // Listen for other devices
    const browser = mdns.createBrowser(mdns.tcp('vaultkeepr-sync'));
    browser.on('serviceUp', (service) => {
      if (service.txtRecord?.deviceId !== this.getDeviceId()) {
        this.peers.set(service.txtRecord.deviceId, {
          address: service.addresses[0],
          port: service.port,
          lastSeen: Date.now()
        });
      }
    });

    browser.start();
  }

  async connectToPeers() {
    for (const [deviceId, peer] of this.peers) {
      try {
        await this.establishConnection(peer);
      } catch (error) {
        console.warn(`Failed to connect to ${deviceId}:`, error);
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

How VaultKeepR Implements Cloud-Free Sync

VaultKeepR's approach combines multiple protocols for maximum reliability:

Local Network Priority: Devices on the same WiFi network sync directly via mDNS discovery and WebSocket connections. This provides instant, high-bandwidth synchronization without internet dependency.

Internet P2P Fallback: When devices are on different networks, WebRTC establishes direct connections through NAT traversal, eliminating the need for cloud storage while maintaining global accessibility.

Mesh Resilience: Each device maintains a partial mesh network with recent peers. If the primary sync target is offline, updates propagate through intermediate devices that have seen both endpoints recently.

Cryptographic Integrity: Every sync message is signed with device-specific keys derived from your master seed phrase. This prevents man-in-the-middle attacks even when using public STUN servers for NAT traversal.

Actionable Implementation Steps

1. Set Up Local Network Discovery

Start with mDNS service advertisement to find devices on your local network. This provides the fastest sync path and works offline.

2. Implement WebRTC Signaling

Create a minimal signaling mechanism using QR codes or local HTTP servers. Avoid centralized signaling servers that create cloud dependencies.

3. Design Conflict Resolution

Implement vector clocks or similar logical timestamps to handle concurrent modifications deterministically across all devices.

4. Add Encryption Layers

Encrypt all sync traffic with keys derived from user credentials, not device-specific keys that could be lost during device replacement.

5. Test Network Partition Scenarios

Ensure your sync logic gracefully handles network splits and merges changes correctly when connectivity resumes.

The Future of Decentralized Sync

Emerging protocols like Hypercore and IPFS are pushing the boundaries of decentralized data synchronization. WebAssembly enables running complex P2P protocols directly in browsers, eliminating the need for native applications.

The convergence of WebRTC improvements, broader WebAssembly adoption, and growing privacy consciousness suggests that cloud-free sync will become the default for privacy-focused applications within the next few years.

As centralized services face increasing scrutiny over data handling and availability, the ability to synchronize data without cloud dependencies becomes a competitive advantage rather than a niche feature.

Top comments (0)