DEV Community

VaultKeepR
VaultKeepR

Posted on

Cross Device Sync Without Cloud: P2P Architecture Guide

Cover

The Cloud Dependency Problem

Your password manager works perfectly on your laptop. But when you switch to your phone, you're locked out of your own data. The traditional solution? Upload everything to Google Drive, iCloud, or Dropbox. But here's the catch: 68% of data breaches in 2023 involved cloud storage misconfigurations.

What if there was a better way? What if your devices could sync directly with each other, cutting out the middleman entirely?

Why P2P Sync Matters More Than Ever

Cloud sync creates three fundamental problems:

  1. Single point of failure: When AWS goes down, your password manager goes down
  2. Privacy exposure: Your encrypted vault sits on someone else's server
  3. Vendor lock-in: Moving providers means rebuilding your entire sync infrastructure

Meanwhile, your devices are already connected to the same WiFi network most of the time. They can talk directly to each other using peer-to-peer protocols, creating a more resilient and private sync system.

Technical Deep Dive: P2P Sync Architecture

Core Components

A robust cross-device sync without cloud requires four key components:

interface P2PSyncSystem {
  discovery: DeviceDiscovery;
  transport: SecureTransport;
  conflictResolution: ConflictResolver;
  storage: LocalStorage;
}
Enter fullscreen mode Exit fullscreen mode

1. Device Discovery

First, devices need to find each other on the network:

class DeviceDiscovery {
  private mdns: MulticastDNS;
  private bluetooth: BluetoothLE;

  async discoverPeers(): Promise<Device[]> {
    // mDNS for local network discovery
    const networkPeers = await this.mdns.browse('_vaultkeepr._tcp');

    // Bluetooth LE for nearby devices
    const bluetoothPeers = await this.bluetooth.scan();

    return [...networkPeers, ...bluetoothPeers];
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Secure Transport Layer

Use WebRTC for encrypted peer-to-peer communication:

class SecureTransport {
  private peerConnection: RTCPeerConnection;

  async establishConnection(peerId: string): Promise<RTCDataChannel> {
    this.peerConnection = new RTCPeerConnection({
      iceServers: [] // No STUN servers needed for local network
    });

    const dataChannel = this.peerConnection.createDataChannel('sync', {
      ordered: true
    });

    // Exchange offer/answer via local discovery
    await this.exchangeSignaling(peerId);

    return dataChannel;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Conflict Resolution

Handle simultaneous edits across devices:

interface SyncRecord {
  id: string;
  timestamp: number;
  deviceId: string;
  data: EncryptedData;
  vectorClock: Map<string, number>;
}

class ConflictResolver {
  resolve(local: SyncRecord, remote: SyncRecord): SyncRecord {
    // Vector clock comparison for ordering
    if (this.happensBefore(local.vectorClock, remote.vectorClock)) {
      return remote;
    }

    if (this.happensBefore(remote.vectorClock, local.vectorClock)) {
      return local;
    }

    // Concurrent edits: use last-writer-wins with device ID tiebreaker
    return local.timestamp > remote.timestamp ? local : 
           local.timestamp === remote.timestamp && local.deviceId > remote.deviceId ? local : remote;
  }
}
Enter fullscreen mode Exit fullscreen mode

VaultKeepR's Approach: Mesh Network Sync

VaultKeepR implements cross-device sync without cloud using a mesh network approach. Here's how it works:

Device Registration

When you add a new device, it generates a unique identity and exchanges public keys with existing devices:

class MeshSync {
  async registerDevice(newDevice: Device): Promise<void> {
    const keyPair = await crypto.subtle.generateKey(
      { name: 'ECDH', namedCurve: 'P-256' },
      true,
      ['deriveKey', 'deriveBits']
    );

    // Broadcast public key to existing devices
    await this.broadcastToMesh({
      type: 'DEVICE_REGISTER',
      deviceId: newDevice.id,
      publicKey: keyPair.publicKey
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Incremental Sync

Only changes are transmitted, not entire databases:

interface SyncDelta {
  added: Record<string, EncryptedEntry>;
  modified: Record<string, EncryptedEntry>;
  deleted: string[];
  timestamp: number;
}

async syncDeltas(since: number): Promise<SyncDelta> {
  const changes = await this.storage.getChangesSince(since);

  return {
    added: changes.filter(c => c.type === 'CREATE'),
    modified: changes.filter(c => c.type === 'UPDATE'),
    deleted: changes.filter(c => c.type === 'DELETE').map(c => c.id),
    timestamp: Date.now()
  };
}
Enter fullscreen mode Exit fullscreen mode

Offline Resilience

Devices store sync queues locally and reconcile when connections are restored:

class OfflineQueue {
  private pendingChanges: SyncDelta[] = [];

  async queueChange(delta: SyncDelta): Promise<void> {
    this.pendingChanges.push(delta);
    await this.storage.persistQueue(this.pendingChanges);

    // Try to sync immediately if peers are available
    this.attemptSync();
  }
}
Enter fullscreen mode Exit fullscreen mode

Implementation Steps You Can Take Today

1. Start with Local Network Discovery

Implement mDNS-based discovery for same-network devices:

npm install mdns-js
Enter fullscreen mode Exit fullscreen mode
import mdns from 'mdns-js';

const browser = mdns.createBrowser(mdns.tcp('myapp'));
browser.on('ready', () => browser.discover());
browser.on('update', (data) => {
  console.log('Found peer:', data);
});
Enter fullscreen mode Exit fullscreen mode

2. Add WebRTC Data Channels

Create encrypted connections between discovered devices:

const pc = new RTCPeerConnection();
const channel = pc.createDataChannel('sync');

channel.onopen = () => {
  console.log('P2P channel established');
};

channel.onmessage = (event) => {
  const syncData = JSON.parse(event.data);
  processSyncMessage(syncData);
};
Enter fullscreen mode Exit fullscreen mode

3. Implement Change Tracking

Track local modifications for efficient sync:

class ChangeTracker {
  private changes: Map<string, Change> = new Map();

  recordChange(id: string, type: 'CREATE' | 'UPDATE' | 'DELETE', data?: any) {
    this.changes.set(id, {
      id,
      type,
      data,
      timestamp: Date.now()
    });
  }

  getChangesSince(timestamp: number): Change[] {
    return Array.from(this.changes.values())
      .filter(change => change.timestamp > timestamp);
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Build Conflict Resolution

Handle simultaneous edits gracefully:

function resolveConflict(local: Entry, remote: Entry): Entry {
  // Simple last-writer-wins
  if (remote.lastModified > local.lastModified) {
    return remote;
  }

  // For ties, prefer the entry with more content
  if (remote.lastModified === local.lastModified) {
    return JSON.stringify(remote).length > JSON.stringify(local).length ? 
           remote : local;
  }

  return local;
}
Enter fullscreen mode Exit fullscreen mode

The Future: Beyond Simple P2P

Cross-device sync without cloud is evolving rapidly. Here's what's coming:

IPFS Integration: Distributed file systems will enable global P2P sync without traditional servers.

Bluetooth Mesh Networks: Low-power sync for IoT devices and wearables.

Satellite P2P: Direct device-to-device sync via satellite networks, bypassing internet infrastructure entirely.

Quantum-Resistant Protocols: Future-proofing P2P sync against quantum computing threats.

The shift away from cloud dependencies isn't just about privacy—it's about building more resilient, user-controlled systems. As developers, we have the tools today to start building these decentralized architectures. The question isn't whether P2P sync will replace cloud sync, but how quickly we can make it the better choice for users who value privacy and control over their data.

Start small, think mesh, and give users back control over their digital lives.

Top comments (0)