'use client'; import { useState, useEffect, useRef } from 'react'; const CHUNK_SIZE = 8 * 1024 * 1024; // 8 MB const PARALLEL_UPLOADS = 4; function bufferToBase64(buffer: ArrayBuffer) { let binary = ''; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } function formatTime(seconds: number) { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; } function formatBytes(bytes: number, decimals = 2) { if (bytes === 0) return '0 Bytes'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } export default function Home() { const [selectedFile, setSelectedFile] = useState(null); const [uploadState, setUploadState] = useState<'idle' | 'processing' | 'uploading' | 'complete'>('idle'); const [progress, setProgress] = useState(0); const [downloadUrl, setDownloadUrl] = useState(''); const [uploadedSize, setUploadedSize] = useState(0); const [uploadSpeed, setUploadSpeed] = useState(0); const [elapsedTime, setElapsedTime] = useState(0); const [etr, setEtr] = useState(0); const [copyState, setCopyState] = useState<'idle' | 'copied'>('idle'); const timerRef = useRef(null); const lastUploadedSizeRef = useRef(0); const uploadedSizeRef = useRef(0); useEffect(() => { uploadedSizeRef.current = uploadedSize; }, [uploadedSize]); useEffect(() => { if (uploadState === 'uploading') { const startTime = Date.now(); lastUploadedSizeRef.current = 0; timerRef.current = setInterval(() => { setElapsedTime((Date.now() - startTime) / 1000); const currentUploadedSize = uploadedSizeRef.current; const speed = currentUploadedSize - lastUploadedSizeRef.current; setUploadSpeed(speed > 0 ? speed : 0); if (speed > 0 && selectedFile) { const remainingSize = selectedFile.size - currentUploadedSize; setEtr(remainingSize / speed); } lastUploadedSizeRef.current = currentUploadedSize; }, 1000); } else if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; setUploadSpeed(0); } return () => { if (timerRef.current) { clearInterval(timerRef.current); } }; }, [uploadState, selectedFile]); const handleFileChange = (event: React.ChangeEvent) => { const files = event.target.files; if (files && files.length > 0) { handleFileSelect(files[0]); } }; const handleDrop = (event: React.DragEvent) => { event.preventDefault(); const files = event.dataTransfer.files; if (files && files.length > 0) { handleFileSelect(files[0]); } }; const handleFileSelect = (file: File) => { setSelectedFile(file); startUploadProcess(file).catch(console.error); } const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); }; const handleCopy = () => { navigator.clipboard.writeText(downloadUrl); setCopyState('copied'); setTimeout(() => setCopyState('idle'), 2000); }; const startUploadProcess = async (file: File) => { if (!file) return; setUploadState('processing'); setProgress(0); setDownloadUrl(''); setUploadedSize(0); setElapsedTime(0); setEtr(0); const fileKey = await window.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']); const numParts = Math.ceil(file.size / CHUNK_SIZE); const createResponse = await fetch('/api/create-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: file.name, size: file.size, num_parts: numParts }), }); const { file_id } = await createResponse.json(); setUploadState('uploading'); const chunks: { index: number; data: Blob }[] = []; for (let i = 0; i < numParts; i++) { const start = i * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const encryptedChunk = await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, fileKey, await chunk.arrayBuffer()); const combinedData = new Blob([iv, new Uint8Array(encryptedChunk)]); chunks.push({ index: i, data: combinedData }); } let uploadedCount = 0; const uploadChunk = async (chunk: { index: number; data: Blob }) => { const formData = new FormData(); formData.append('file_id', file_id); formData.append('part_index', chunk.index.toString()); formData.append('encrypted_chunk', chunk.data); await fetch('/api/upload-part', { method: 'POST', body: formData }); uploadedCount++; const currentSize = uploadedCount * CHUNK_SIZE > file.size ? file.size : uploadedCount * CHUNK_SIZE; setUploadedSize(currentSize); setProgress(Math.round((uploadedCount / numParts) * 100)); }; const queue = [...chunks]; const active: Promise[] = []; while (queue.length > 0 || active.length > 0) { while (active.length < PARALLEL_UPLOADS && queue.length > 0) { const task = queue.shift()!; const promise = uploadChunk(task).finally(() => { const index = active.indexOf(promise); if (index > -1) active.splice(index, 1); }); active.push(promise); } await Promise.race(active); } const completeResponse = await fetch('/api/complete-upload', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_id }), }); const { download_token } = await completeResponse.json(); const exportedKey = await window.crypto.subtle.exportKey('raw', fileKey); const keyString = bufferToBase64(exportedKey); setUploadState('complete'); setDownloadUrl(`${window.location.origin}/download/${file_id}?token=${download_token}#${keyString}`); }; return (

LockLoad

Upload large files, encrypted and stored on Discord. 7-day retention.

{uploadState === 'idle' && (
)} {(uploadState === 'processing' || uploadState === 'uploading') && (

Uploading...

{selectedFile?.name}

{progress}% {formatBytes(uploadedSize)} / {selectedFile ? formatBytes(selectedFile.size) : '0 Bytes'}

Speed

{formatBytes(uploadSpeed)}/s

Elapsed

{formatTime(elapsedTime)}

ETR

{etr > 0 && etr < Infinity ? formatTime(etr) : '--:--'}

)} {uploadState === 'complete' && (

Upload Complete!

Your file is ready. Save the link below.

This link contains the decryption key. Keep it safe!

)}
); }