import {createCryptoBackend} from './crypto-backend.js' import {getServers, pickRandomServer} from './servers.js' import {createProgressRing} from './progress.js' import { newXFTPAgent, closeXFTPAgent, uploadFile, encodeDescriptionURI, type EncryptedFileMetadata } from '../src/agent.js' const MAX_SIZE = 100 * 1024 * 1024 export function initUpload(app: HTMLElement) { app.innerHTML = `

SimpleX File Transfer

Drag & drop a file here

or

Max 100 MB

` const dropZone = document.getElementById('drop-zone')! const fileInput = document.getElementById('file-input') as HTMLInputElement const progressStage = document.getElementById('upload-progress')! const completeStage = document.getElementById('upload-complete')! const errorStage = document.getElementById('upload-error')! const progressContainer = document.getElementById('progress-container')! const statusText = document.getElementById('upload-status')! const cancelBtn = document.getElementById('cancel-btn')! const shareLink = document.getElementById('share-link') as HTMLInputElement const copyBtn = document.getElementById('copy-btn')! const errorMsg = document.getElementById('error-msg')! const retryBtn = document.getElementById('retry-btn')! let aborted = false let pendingFile: File | null = null dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over') }) dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over')) dropZone.addEventListener('drop', e => { e.preventDefault() dropZone.classList.remove('drag-over') const f = e.dataTransfer?.files[0] if (f) startUpload(f) }) fileInput.addEventListener('change', () => { if (fileInput.files?.[0]) startUpload(fileInput.files[0]) }) retryBtn.addEventListener('click', () => { if (pendingFile) startUpload(pendingFile) }) function showStage(stage: HTMLElement) { for (const s of [dropZone, progressStage, completeStage, errorStage]) s.hidden = true stage.hidden = false } function showError(msg: string) { errorMsg.textContent = msg showStage(errorStage) } async function startUpload(file: File) { pendingFile = file aborted = false if (file.size > MAX_SIZE) { showError(`File too large (${formatSize(file.size)}). Maximum is 100 MB.`) return } if (file.size === 0) { showError('File is empty.') return } showStage(progressStage) const ring = createProgressRing() progressContainer.innerHTML = '' progressContainer.appendChild(ring.canvas) statusText.textContent = 'Encrypting…' const backend = createCryptoBackend() const agent = newXFTPAgent() cancelBtn.onclick = () => { aborted = true backend.cleanup().catch(() => {}) closeXFTPAgent(agent) showStage(dropZone) } try { const fileData = new Uint8Array(await file.arrayBuffer()) if (aborted) return const encrypted = await backend.encrypt(fileData, file.name, (done, total) => { ring.update(done / total * 0.3) }) if (aborted) return statusText.textContent = 'Uploading…' const metadata: EncryptedFileMetadata = { digest: encrypted.digest, key: encrypted.key, nonce: encrypted.nonce, chunkSizes: encrypted.chunkSizes } const servers = getServers() const server = pickRandomServer(servers) const result = await uploadFile(agent, server, metadata, { readChunk: (off, sz) => backend.readChunk(off, sz), onProgress: (uploaded, total) => { ring.update(0.3 + (uploaded / total) * 0.7) } }) if (aborted) return const url = window.location.origin + window.location.pathname + '#' + result.uri shareLink.value = url showStage(completeStage) copyBtn.onclick = () => { navigator.clipboard.writeText(url).then(() => { copyBtn.textContent = 'Copied!' setTimeout(() => { copyBtn.textContent = 'Copy' }, 2000) }) } } catch (err: any) { if (!aborted) showError(err?.message ?? String(err)) } finally { await backend.cleanup().catch(() => {}) closeXFTPAgent(agent) } } } function formatSize(bytes: number): string { if (bytes < 1024) return bytes + ' B' if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB' return (bytes / (1024 * 1024)).toFixed(1) + ' MB' }