Secure, encrypted peer-to-peer file sharing with zero server dependency
SwiftDrop is a modern, production-ready peer-to-peer file transfer application that enables direct file sharing between devices without any server intermediary. Built with React, TypeScript, and WebRTC, it provides end-to-end encrypted transers with real-time progress tracking, pause/resume capabilities, and support for local network optimization.
- Zero Server Transfer: Files transfer directly between devices using WebRTC DataChannels
- End-to-End Encryption: AES-GCM 256-bit encryption with keys generated and exchanged client-side
- Multi-File Support: Select and transfer unlimited files in a single session
- Resumable File Transfers: Pause and resume transfers with chunk-level granularity
- Local Network Mode: Optimized transfers on same Wi-Fi networks
- Transfer History: IndexedDB-based persistent history tracking
- Speed & ETA: Real-time transfer speed and estimated time remaining
- Modern UI: Clean, minimalist design with Space Grotesk typography
- Algorithm: AES-GCM 256-bit encryption using native WebCrypto API
- Key Generation: Unique encryption keys generated client-side for each session
- Key Exchange: Keys shared duing WebRTC signaling (never stored permanently)
- Data Flow: All file data encrypted before transmission, decrypted after reception
- IV Randomization: Random initialization Vector (IV) for each encryption operation
- Technology: WebRTC DataChannels with RTCPeerConnection
- Chunk Size: 16KB chunks for optimal transfer performance
- NAT Traversal: STUN servers for connection through firewalls
- No Server Storage: Files never stored on any intermediary server
- Binary Transfer: Efficient binary data transmission with Array Buffer
- Unlimited Files: No limit on number of files per session
- File Management: Add/remove individual files before transfer
- Aggregate Progress: Combined progress tracking across all files
- Sequential Transfer: Files transferred one-by-one with full encryption
- File Metadata: Name, size, type preserved during transfer
- Pause/Resume: Control transfer progress with pause and resume buttons
- Chunk State: Track which chunks have been sent/received
- State Persistence: Transfer state saved to IndexedDB for reload recovery
- Peer Coordination: Pause/resume signals synchronized between sender and receiver
- Granular Control: Resume from exact chunk where transfer was paused
- Wi-Fi Optimization: Prioritizes local ICE candidates for same-network transfers
- Faster Speeds: Lower latency and higher throughput on local networks
- Automatic Detection: Network mode flag included in connection data
- Security Maintained: Full E2E encryption even on local networks
- Fallback Support: Automatic fallback to STUN servers if needed
- Transfer Speed: Live calculation in B/s, KB/s, MB/s, GB/s
- ETA Display: Estimated time remaining with smart formatting
- Byte Progress: Sent/Total bytes with percentage
- Speed Calculation: Moving average over 100ms windows
- Visual Feedback: Progress bar with shimmer animation
- Persistent Storage: IndexedDB-based history (metadata only, not file content)
- Detailed Records: File name, size, timestamp, type (sent/received), status
- Sortable View: Chronologically sorted with most recent first
- Storage Management: Automatic cleanup of old transfer states (24-hour retention)
- Privacy: No file content stored, only transfer metadata
- React 18.3.1: Declarative UI with hooks and functional components
- TypeScript 5.8: Strong typing for enhanced code quality and IDE support
- Vite 7.0: Lightning-fast build tool with HMR (Hot Module Replacement)
- TailwindCSS 3.4: Utility-first CSS framework for rapid UI development
- Space Grotesk: Modern geometric sans-serif font from Google Fonts
- Lucide React: Beautiful, customizable icon library
- WebRTC: Native browser API for peer-to-peer connections
RTCPeerConnection: Manages peer-to-peer connectionRTCDataChannel: Binary data transfer channel- STUN Servers:
stun.l.google.com:19302,stun1.l.google.com:19302
- WebCrypto API: Native browser API for cryptographic operations
crypto.subtle.generateKey: AES-GCM key generationcrypto.subtle.encrypt/decrypt: File encryption/decryptioncrypto.getRandomValues: Secure random IV generation
- IndexedDB: Native browser database for transfer history and resumable state
transfersstore: Transfer history recordstransferStatesstore: Resumable transfer state with chunk progress
- React State: Local component state management with hooks
useState: Component stateuseRef: Mutable references for performance trackinguseEffect: Side effects and cleanup
- qrcode.react: QR code generation for easy connection sharing
- uuid: Unique session ID generation
┌─────────────────────────────────────────────────────────────┐
│ Browser A (Sender) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ React UI │◄─►│ WebRTC Layer │◄─►│ Crypto Layer │ │
│ │ │ │ │ │ │ │
│ │ - File Select│ │ - DataChannel│ │ - Key Gen │ │
│ │ - Progress │ │ - Chunks │ │ - Encryption │ │
│ │ - Controls │ │ - State Mgmt │ │ - IV Generation │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
│ │ │ │ │
│ └────────────────────┴────────────────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ IndexedDB Store │ │
│ │ - History │ │
│ │ - Resume State │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ P2P Connection
│ (Encrypted)
▼
┌─────────────────────────────────────────────────────────────┐
│ Browser B (Receiver) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ React UI │◄─►│ WebRTC Layer │◄─►│ Crypto Layer │ │
│ │ │ │ │ │ │ │
│ │ - Progress │ │ - DataChannel│ │ - Key Import │ │
│ │ - Download │ │ - Chunks │ │ - Decryption │ │
│ │ - Controls │ │ - Reassembly │ │ - IV Extraction │ │
│ └──────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Sender Receiver
│ │
├─► Generate AES-GCM Key │
├─► Create WebRTC Offer │
├─► Gather ICE Candidates │
├─► Export Key to Base64 │
├─► Package: {offer, ICE, key, mode} │
├─► Display as QR + Copyable Text │
│ │
│ Manual Copy-Paste │
│ ────────────────────────────────────► │
│ │
│ ├─► Parse Connection Data
│ ├─► Import Key from Base64
│ ├─► Create WebRTC Answer
│ ├─► Add ICE Candidates
│ │
│ ◄────── Answer (Manual Exchange) ───── │
│ │
├─► Process Answer │
├─► WebRTC Connection Established │
│ ◄═══════════════════════════════════► │
│ Secure P2P Connection │
1. Metadata Phase
Sender ──► {type:'metadata', name, size, mimeType} ──► Receiver
2. Encryption Phase
Sender:
- Read file as ArrayBuffer
- Generate random IV (12 bytes)
- Encrypt: AES-GCM(data, key, IV)
- Combine: [IV | encrypted_data]
3. Chunking Phase
Sender:
- Split combined data into 16KB chunks
- Track: currentChunk / totalChunks
4. Transfer Phase (per chunk)
Sender ──► Encrypted Chunk[i] ──► Receiver
- Wait for buffer availability
- Send chunk via DataChannel
- Update progress: (i * CHUNK_SIZE / total)
- Check pause state
5. Reassembly Phase
Receiver:
- Collect all chunks
- Combine into single ArrayBuffer
- Extract: IV (first 12 bytes)
- Extract: encrypted_data (remaining bytes)
6. Decryption Phase
Receiver:
- Decrypt: AES-GCM(encrypted_data, key, IV)
- Create File object
- Trigger browser download
7. Completion
Sender ──► {type:'end'} ──► Receiver
Both: Save to IndexedDB history
src/api/webrtc.ts - WebRTCTransfer Class
class WebRTCTransfer {
// Properties
- peerConnection: RTCPeerConnection
- dataChannel: RTCDataChannel
- encryptionKey: CryptoKey
- isPaused: boolean
- useLocalNetwork: boolean
// Connection Setup
+ constructor(useLocalNetwork)
+ createOffer(): RTCSessionDescriptionInit
+ handleOffer(offer): RTCSessionDescriptionInit
+ handleAnswer(answer): void
+ addIceCandidate(candidate): void
+ getLocalIceCandidates(): Promise<RTCIceCandidate[]>
// Transfer Control
+ sendFile(file, fileState?): Promise<void>
+ sendFiles(files, states?): Promise<void>
+ pauseTransfer(): void
+ resumeTransfer(): void
+ sendPauseSignal(): void
+ sendResumeSignal(): void
// Callbacks
+ onProgress(callback): void
+ onFileReceived(callback): void
+ onConnectionState(callback): void
// Lifecycle
+ close(): void
}src/api/encryption.ts - Encryption Service
// Key Management
+ generateEncryptionKey(): Promise<CryptoKey>
+ exportKey(key): Promise<string> // Base64
+ importKey(keyString): Promise<CryptoKey>
// Encryption Operations
+ encryptData(data, key): Promise<{encrypted, iv}>
+ decryptData(encryptedData, key, iv): Promise<ArrayBuffer>src/api/storage.ts - Storage Service
class StorageService {
// Initialization
+ init(): Promise<void>
// Transfer History
+ addTransfer(record): Promise<void>
+ getAllTransfers(): Promise<TransferRecord[]>
+ getRecentTransfers(limit): Promise<TransferRecord[]>
+ deleteTransfer(id): Promise<void>
+ clearHistory(): Promise<void>
// Resumable State
+ saveTransferState(state): Promise<void>
+ getTransferState(sessionId): Promise<MultiFileTransferState>
+ deleteTransferState(sessionId): Promise<void>
+ getAllTransferStates(): Promise<MultiFileTransferState[]>
+ clearOldTransferStates(maxAge): Promise<void>
}src/pages/Transfer.tsx - Main Application
- Mode selection (Send / Receive / History)
- Network mode toggle (Internet / Local)
- File selection and management
- Transfer progress and controls
- Connection status display
- Speed and ETA calculations
src/components/FileDropZone.tsx - File Upload
- Drag-and-drop file selection
- Multiple file support
- File list with individual removal
- Total size calculation
- Visual drag feedback
src/components/ProgressBar.tsx - Progress Display
- Percentage progress bar
- Transfer speed indicator
- ETA display
- Sent/Total bytes
- Status color coding
src/components/ConnectionCode.tsx - Connection Sharing
- QR code generation
- Session code display
- Copy-to-clipboard functionality
- Connection data formatting
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun1.l.google.com:19302' }
],
iceTransportPolicy: useLocalNetwork ? 'all' : 'all'
};
const pc = new RTCPeerConnection(config);// Sender creates channel
const dataChannel = pc.createDataChannel('fileTransfer', {
ordered: true,
binaryType: 'arraybuffer'
});
// Receiver listens for channel
pc.ondatachannel = (event) => {
const dataChannel = event.channel;
dataChannel.binaryType = 'arraybuffer';
};// Sender
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const iceCandidates = await gatherAllCandidates();
// Package for sharing
const signalData = JSON.stringify({
offer,
iceCandidates,
encryptionKey: await exportKey(key),
isLocalNetwork: useLocalNetwork
});
// Receiver
const data = JSON.parse(pastedSignalData);
await pc.setRemoteDescription(data.offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
for (const candidate of data.iceCandidates) {
await pc.addIceCandidate(candidate);
}const key = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256
},
true, // extractable
['encrypt', 'decrypt']
);// Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// Encrypt file data
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
fileArrayBuffer
);
// Combine IV + encrypted data
const combined = new Uint8Array(12 + encrypted.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(encrypted), 12);// Extract IV and encrypted data
const iv = receivedData.slice(0, 12);
const encryptedData = receivedData.slice(12);
// Decrypt
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encryptedData
);
// Create file
const file = new File([decrypted], fileName, { type: mimeType });// Track progress over time
const now = Date.now();
const timeDiff = (now - lastUpdateTime) / 1000; // seconds
const bytesDiff = currentBytes - lastBytes;
// Calculate instantaneous speed
const speed = bytesDiff / timeDiff; // bytes per second
// Calculate ETA
const remainingBytes = totalBytes - currentBytes;
const eta = speed > 0 ? remainingBytes / speed : 0; // seconds
// Smooth display with moving average
if (timeDiff > 0.1) { // Update every 100ms
updateDisplay(speed, eta);
}interface FileTransferState {
fileId: string;
fileName: string;
chunks: ChunkState[];
totalChunks: number;
completedChunks: number;
isPaused: boolean;
isCompleted: boolean;
}
// Save to IndexedDB
await storageService.saveTransferState({
sessionId: uuid(),
files: fileStates,
currentFileIndex: 0,
totalFiles: files.length,
isPaused: false,
isCompleted: false,
startTime: Date.now(),
lastUpdateTime: Date.now()
});
// Resume from saved state
const savedState = await storageService.getTransferState(sessionId);
const startChunk = savedState.files[0].completedChunks;
// Continue from startChunk...- Node.js 16+ (for development)
- Modern web browser with:
- WebRTC support (Chrome 90+, Firefox 88+, Safari 15+)
- WebCrypto API support
- IndexedDB support
# Clone the repository
git clone https://github.com/yourusername/swiftdrop.git
cd swiftdrop
# Install dependencies
npm install
# Build for production
npm run build# Start development server
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview- Open SwiftDrop in your browser
- Click "Send Files"
- Select Network Mode:
- Internet: Standard transfer via STUN servers (works anywhere)
- Local: Optimized for same Wi-Fi network (faster)
- Add Files:
- Drag and drop files onto the upload zone
- Or click to browse and select files
- Remove individual files if needed
- Generate Code:
- Click "Generate Connection Code"
- QR code and session code are displayed
- Share Connection:
- Show QR code to receiver
- Or copy connection data and send via any messaging app
- Wait for Connection:
- Status shows "waiting for connection"
- Receiver must paste/scan your code
- Start Transfer:
- Click "Start Transfer" when connected
- Monitor progress, speed, and ETA
- Use pause/resume as needed
- Complete:
- Files are saved to receiver's downloads
- Transfer record saved to history
- Open SwiftDrop in your browser
- Click "Receive Files"
- Enter Connection Data:
- Paste the connection data from sender
- Or scan QR code (mobile devices)
- Click "Connect and Receive":
- Connection establishes automatically
- Transfer starts immediately
- Monitor Progress:
- View real-time speed and ETA
- Wait for transfer to complete
- Download:
- Files automatically download to your browser's download folder
- Transfer record saved to history
- Click "View Transfer History" from home screen
- See all past transfers:
- Sent and received files
- File names, sizes, timestamps
- Transfer status (completed/failed)
- Sort by date: Most recent transfers at the top
- Algorithm: AES-GCM (Authenticated Encryption with Associated Data)
- Key Size: 256 bits (extremely secure)
- IV: 96 bits, randomly generated per encryption
- Authentication: Built-in authentication tag prevents tampering
- Generation: Client-side using WebCrypto API (cryptographically secure)
- Exchange: Keys shared during WebRTC signaling (visible to user)
- Storage: Keys never stored permanently (only in memory during session)
- Lifetime: One key per session, discarded after transfer
- In Transit: All data encrypted before sending, decrypted after receiving
- At Rest: No file data stored on servers or IndexedDB (only metadata)
- Peer-to-Peer: Direct connection between devices (no intermediary)
- Manual Exchange: Connection data manually copied/pasted by user
- Visibility: User can inspect all connection data
- Production Note: For automatic signaling, use secure WebSocket with TLS
-
Manual Signaling
- Requires copy-paste of connection data
- No automatic peer discovery
- Solution: Implement WebSocket signaling server
-
NAT Traversal
- Uses public STUN servers only
- May fail on symmetric NATs or restrictive firewalls
- Solution: Add TURN server for relay fallback
-
Sequential File Transfer
- Files transferred one-by-one
- No parallel transfer support
- Solution: Implement concurrent DataChannels
-
Browser Storage Limits
- IndexedDB subject to browser quotas (typically 50MB-10GB)
- Resumable state may be cleared
- Solution: Warn users about storage limits
-
Browser Dependency
- Requires modern browser with WebRTC support
- No support for older browsers
- Solution: Progressive enhancement or fallback UI
- Automatic WebSocket signaling server
- TURN server integration for better NAT traversal
- Parallel multi-file transfer
- Folder upload and download
- Transfer speed optimization with multiple channels
- Mobile app versions (React Native)
- Real-time chat during transfer
- File preview before download
- Compression support
- Bandwidth throttling
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
For issues, questions, or suggestions:
- Open a GitHub Issue
- Email: iram.faiaz99@gmail.com
SwiftDrop - Fast, secure, peer-to-peer file sharing made simple.
Made with ❤️ using React, WebRTC, and WebCrypto