Skip to main content

Device Pairing System

ErikrafT Drop’s device pairing feature allows devices to maintain persistent connections across different networks. Paired devices can discover each other regardless of their current network location, providing seamless file sharing capabilities.

Pairing Architecture

Room Secret System

Device pairing uses cryptographically secure room secrets:
// From ws-server.js lines 223-224
let roomSecret = randomizer.getRandomString(256);
let pairKey = this._createPairKey(sender, roomSecret);

Persistent Storage

Paired device information is stored using IndexedDB:
// From persistent-storage.js
class PersistentStorage {
    async saveRoomSecret(roomSecret) {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open('pairdrop-db');
            request.onsuccess = (event) => {
                const db = event.target.result;
                const transaction = db.transaction(['room-secrets'], 'readwrite');
                const store = transaction.objectStore('room-secrets');
                store.put({ id: roomSecret, secret: roomSecret });
            };
        });
    }
}

Pairing Process

1. Initiation Phase

Pair Device Dialog

// From ui.js lines 1954-1956
_pairDeviceInitiate() {
    Events.fire('pair-device-initiate');
}

Server-Side Initiation

// From ws-server.js lines 222-237
_onPairDeviceInitiate(sender) {
    let roomSecret = randomizer.getRandomString(256);
    let pairKey = this._createPairKey(sender, roomSecret);

    if (sender.pairKey) {
        this._removePairKey(sender.pairKey);
    }
    sender.pairKey = pairKey;

    this._send(sender, {
        type: 'pair-device-initiated',
        roomSecret: roomSecret,
        pairKey: pairKey
    });
    this._joinSecretRoom(sender, roomSecret);
}

2. Key Generation

Secure Pair Key Creation

// From ws-server.js lines 327-340
_createPairKey(creator, roomSecret) {
    let pairKey;
    do {
        // Generate 6-digit numeric key
        pairKey = crypto.randomInt(1000000, 1999999).toString().substring(1);
    } while (pairKey in this._roomSecrets)

    this._roomSecrets[pairKey] = {
        roomSecret: roomSecret,
        creator: creator
    }

    return pairKey;
}

Key Format

  • Length: 6 digits
  • Format: Numeric string (e.g., “123456”)
  • Uniqueness: Guaranteed unique within server instance
  • Expiration: Temporary, expires after pairing or timeout

3. QR Code Generation

QR Code Display

// From ui.js lines 1966-1981
_setKeyAndQRCode() {
    this.$key.innerText = `${this.pairKey.substring(0, 3)} ${this.pairKey.substring(3, 6)}`

    // Display QR code for the url
    const qr = new QRCode({
        content: this._getPairUrl(),
        width: 130,
        height: 130,
        color: {
            dark: "#000000",
            light: "#FFFFFF"
        },
        ecl: "L",
        join: true
    });
    this.$qrCode.innerHTML = qr.svg();
}

Pair URL Generation

// From ui.js lines 1983-2002
_getPairUrl() {
    const url = new URL(location.href);
    url.searchParams.delete('pair_key');
    url.hash = '';
    if (this.pairKey) {
        url.searchParams.append('pair_key', this.pairKey);
    }
    return url.href;
}

Joining a Pair

1. Key Entry

// From ui.js lines 2004-2014
_submit() {
    let inputKey = this.inputKeyContainer._getInputKey();
    this._pairDeviceJoin(inputKey);
}

_pairDeviceJoin(pairKey) {
    if (/^\d{6}$/g.test(pairKey)) {
        Events.fire('pair-device-join', pairKey);
        this.inputKeyContainer.focusLastChar();
    }
}

2. Server Validation

// From ws-server.js lines 239-265
_onPairDeviceJoin(sender, message) {
    if (sender.rateLimitReached()) {
        this._send(sender, { type: 'join-key-rate-limit' });
        return;
    }

    if (!this._roomSecrets[message.pairKey] || sender.id === this._roomSecrets[message.pairKey].creator.id) {
        this._send(sender, { type: 'pair-device-join-key-invalid' });
        return;
    }

    const roomSecret = this._roomSecrets[message.pairKey].roomSecret;
    const creator = this._roomSecrets[message.pairKey].creator;
    this._removePairKey(message.pairKey);
    
    this._send(sender, {
        type: 'pair-device-joined',
        roomSecret: roomSecret,
        peerId: creator.id
    });
    this._send(creator, {
        type: 'pair-device-joined',
        roomSecret: roomSecret,
        peerId: sender.id
    });
    this._joinSecretRoom(sender, roomSecret);
    this._removePairKey(sender.pairKey);
}

3. Pair Confirmation

// From ui.js lines 2016-2037
_onPairDeviceJoined(peerId, roomSecret) {
    // abort if peer is another tab on the same browser
    if (BrowserTabsConnector.peerIsSameBrowser(peerId)) {
        this._cleanUp();
        return;
    }

    // save pairPeer and wait for it to connect
    this.pairPeer = {
        "peerId": peerId,
        "roomSecret": roomSecret
    };

    // save roomSecret
    PersistentStorage
        .saveRoomSecret(roomSecret)
        .then(_ => {
            Events.fire('notify-user', Localization.getTranslation("notifications.pairing-success"));
            this.hide();
        })
        .catch(_ => {
            Events.fire('notify-user', Localization.getTranslation("notifications.pairing-not-persistent"));
        });
}

Paired Device Management

Storage Structure

// IndexedDB stores paired device information
{
    id: "room-secret-256-char-string",
    secret: "256-character-encrypted-room-secret",
    displayName: "User-chosen-device-name",
    autoAccept: false,
    timestamp: 1640995200000
}

Device List Management

// From ui.js lines 2162-2243
async _initDOM() {
    const roomSecretsEntries = await PersistentStorage.getAllRoomSecretEntries();

    roomSecretsEntries.forEach(roomSecretsEntry => {
        let $pairedDevice = document.createElement('div');
        $pairedDevice.classList.add('paired-device');
        
        // Device display information
        const displayDiv = document.createElement('div');
        displayDiv.className = 'display-name';
        displayDiv.textContent = roomSecretsEntry.displayName;
        
        // Auto-accept toggle
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.checked = roomSecretsEntry.autoAccept;
        input.addEventListener('change', e => {
            PersistentStorage.updateRoomSecret(roomSecretsEntry.id, {
                autoAccept: e.target.checked
            });
        });
        
        $pairedDevice.appendChild(displayDiv);
        $pairedDevice.appendChild(input);
        this.$pairedDevicesWrapper.appendChild($pairedDevice);
    });
}

Auto-Accept Feature

// From network.js lines 841-847
_respondToFileTransferRequest(accepted) {
    this.sendJSON({type: 'files-transfer-response', accepted: accepted});
    if (accepted) {
        this._requestAccepted = this._requestPending;
        this._totalBytesReceived = 0;
        this._filesReceived = [];
    }
    this._requestPending = null;
}

Security Features

Cryptographic Security

  • Room Secrets: 256-character cryptographically random strings
  • Hash Validation: SHA3-512 hashing for integrity verification
  • Rate Limiting: 10 pairing attempts per 10 seconds
  • Temporary Keys: Pair keys expire after use

Privacy Protection

// From peer.js lines 128-136
_setPeerId(request) {
    const searchParams = new URL(request.url, "http://server").searchParams;
    let peerId = searchParams.get('peer_id');
    let peerIdHash = searchParams.get('peer_id_hash');
    if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) {
        this.id = peerId;
    } else {
        this.id = crypto.randomUUID();
    }
}

Cross-Network Discovery

Persistent Connections

Paired devices maintain discoverability across different networks:
  1. Local Network: Automatic discovery via IP-based rooms
  2. Remote Networks: Discovery via shared room secrets
  3. Network Switching: Seamless transition between networks
  4. Offline Support: Pairing information persists offline

Room Secret Synchronization

// From ws-server.js lines 188-198
_onRoomSecrets(sender, message) {
    if (!message.roomSecrets) return;

    const roomSecrets = message.roomSecrets.filter(roomSecret => {
        return /^[\x00-\x7F]{64,256}$/.test(roomSecret);
    });

    if (!roomSecrets) return;
    this._joinSecretRooms(sender, roomSecrets);
}

Pairing Management

Edit Paired Devices

// From ui.js lines 2141-2252
class EditPairedDevicesDialog extends Dialog {
    constructor() {
        super('edit-paired-devices-dialog');
        this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
        
        Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
        Events.on('keydown', e => this._onKeyDown(e));
    }
}

Unpairing Devices

// From ui.js lines 2221-2240
input.addEventListener('click', e => {
    PersistentStorage
        .deleteRoomSecret(roomSecretsEntry.id)
        .then(roomSecret => {
            Events.fire('room-secrets-deleted', [roomSecret]);
            Events.fire('evaluate-number-room-secrets');
            $pairedDevice.innerText = '';
        });
});

Use Cases

Personal Device Ecosystem

  • Phone to Computer: Quick photo and document transfer
  • Tablet to Laptop: Seamless file synchronization
  • Work to Home: Access files across different networks

Family Sharing

  • Parent-Child Devices: Easy family file sharing
  • Multi-Device Households: Shared family devices
  • Guest Access: Temporary pairing for visitors

Professional Use

  • Team Collaboration: Persistent team device connections
  • Conference Sharing: Quick setup for presentations
  • Remote Work: Home-office device integration

Troubleshooting

Common Issues

  • Invalid Key: Ensure 6-digit key is entered correctly
  • Expired Key: Pair keys expire after use or timeout
  • Network Issues: Both devices need internet connection for pairing
  • Browser Storage: Ensure IndexedDB is enabled and not cleared

Recovery Options

  • Re-pairing: Devices can be re-paired if needed
  • Backup: Export/import pairing information (planned feature)
  • Manual Discovery: Use local network discovery if pairing fails
This device pairing system provides a robust, secure method for maintaining persistent connections between devices across different networks while ensuring user privacy and security.