From 489224022248e2c26ef87457770fb86f24563480 Mon Sep 17 00:00:00 2001 From: Kumi Date: Wed, 18 Dec 2024 17:13:43 +0100 Subject: [PATCH] feat: Add support for multiple file transfers Enhances the web application to allow users to upload and download multiple files simultaneously using WebTorrent. Updates UI and messages to reflect support for multiple files. Adjusts progress indicators for batch file transfers and updates mnemonic generation logic for compatibility. Bumps application version to 0.1.0 for feature release. --- README.md | 1 + package.json | 2 +- public/js/index.js | 99 +++++++++++++++++++--------------------------- views/index.ejs | 23 +++++------ 4 files changed, 54 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 2a7b89d..21580b4 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Transfer.coffee is a simple Node.js web application that allows users to share f ## Features - Peer-to-peer file sharing using WebTorrent +- Transfer multiple files at once - Mnemonic seed generation for easy file sharing - Optional STUN and TURN server configuration for NAT traversal - Progress indicators for file upload and download diff --git a/package.json b/package.json index 416842d..2de3389 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "transfercoffee", - "version": "0.0.2", + "version": "0.1.0", "description": "A WebTorrent-based file transfer application", "main": "app.js", "type": "module", diff --git a/public/js/index.js b/public/js/index.js index 257f4d5..8835b1f 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -11,17 +11,17 @@ async function getRTCIceServers() { } } -async function uploadFile(trackerUrl) { +async function uploadFiles(trackerUrl) { const fileInput = document.getElementById("fileInput"); - const file = fileInput.files[0]; + const files = Array.from(fileInput.files); const uploadStats = document.getElementById("uploadStats"); const uploadSection = document.getElementById("uploadSection"); const downloadSection = document.getElementById("downloadSection"); const copyButton = document.getElementById("copyButton"); const uploadButton = document.getElementById("uploadButton"); - if (!file) { - alert("Please select a file to upload."); + if (!files.length) { + alert("Please select at least one file to upload."); return; } @@ -29,6 +29,8 @@ async function uploadFile(trackerUrl) { uploadSection.style.display = "block"; uploadButton.disabled = true; + uploadStats.innerHTML = ''; + try { const rtcConfig = { iceServers: await getRTCIceServers(), @@ -39,17 +41,16 @@ async function uploadFile(trackerUrl) { rtcConfig: rtcConfig, }; - client.seed(file, opts, async (torrent) => { + client.seed(files, opts, async (torrent) => { try { const response = await fetch(`/generate-mnemonic/${torrent.infoHash}`); if (!response.ok) throw new Error("Failed to generate mnemonic"); const data = await response.json(); const uploadResult = document.getElementById("uploadResult"); - const downloadUrl = `${window.location.origin - }/${data.mnemonic.replaceAll(" ", ".")}`; + const downloadUrl = `${window.location.origin}/${data.mnemonic.replaceAll(" ", ".")}`; history.pushState({}, "", `/${data.mnemonic.replaceAll(" ", ".")}`); - uploadResult.innerHTML = `Seeding file. Share this mnemonic: ${data.mnemonic} -
Note that the file will be available for download only as long as you keep this page open.`; + uploadResult.innerHTML = `Seeding files. Share this mnemonic: ${data.mnemonic} +
Note that the files will be available for download only as long as you keep this page open.`; copyButton.style.display = "inline-block"; copyButton.setAttribute("data-url", downloadUrl); } catch (error) { @@ -80,7 +81,7 @@ async function uploadFile(trackerUrl) { }, 1000); }); } catch (error) { - console.error("Error sharing file:", error); + console.error("Error sharing files:", error); } } @@ -101,7 +102,7 @@ async function sha256(str) { .join(""); } -async function downloadFile(trackerUrl) { +async function downloadFiles(trackerUrl) { const mnemonicInput = document.getElementById("mnemonicInput").value; const downloadProgressBar = document.getElementById("downloadProgressBar"); const downloadButton = document.getElementById("downloadButton"); @@ -134,68 +135,48 @@ async function downloadFile(trackerUrl) { }; client.add(torrentId, opts, (torrent) => { - torrent.files[0].getBlob((err, blob) => { - if (err) { - downloadResult.innerHTML = `Error: ${err.message}`; - return; - } + torrent.files.forEach((file) => { + file.getBlob((err, blob) => { + if (err) { + downloadResult.innerHTML = `Error: ${err.message}`; + return; + } - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = torrent.files[0].name; - a.click(); - - downloadResult.innerHTML = `File downloaded: ${torrent.files[0].name}`; + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = file.name; + a.click(); + }); }); torrent.on("download", () => { - const progress = Math.round( - (torrent.downloaded / torrent.length) * 100 - ); + const progress = Math.round((torrent.downloaded / torrent.length) * 100); downloadProgressBar.style.width = `${progress}%`; downloadProgressBar.textContent = `${progress}%`; }); - }); - setInterval(() => { - if (client.get(torrentId)) { - const torrent = client.get(torrentId); - const progress = Math.round( - (torrent.downloaded / torrent.length) * 100 - ); + setInterval(() => { + const progressText = torrent.files.map(file => `${file.name}`).join(', '); + const progress = Math.round((torrent.downloaded / torrent.length) * 100); + downloadResult.innerHTML = `Downloading: - ${torrent.files[0].name} + ${progressText}
-
Status: ${torrent.done ? "Completed, seeding" : "Downloading" - } +
Status: ${torrent.done ? "Completed, seeding" : "Downloading"}
Peers: ${torrent.numPeers}
-
Downloaded: ${( - torrent.downloaded / - (1024 * 1024) - ).toFixed(2)} MB / ${(torrent.length / (1024 * 1024)).toFixed( - 2 - )} MB (${progress}%) -
Speed: ${( - torrent.downloadSpeed / - (1024 * 1024) - ).toFixed(2)} MB/s +
Downloaded: ${(torrent.downloaded / (1024 * 1024)).toFixed(2)} MB / ${(torrent.length / (1024 * 1024)).toFixed(2)} MB (${progress}%) +
Speed: ${(torrent.downloadSpeed / (1024 * 1024)).toFixed(2)} MB/s
ETA: ${(torrent.timeRemaining / 1000).toFixed(0)} seconds
-
Uploaded: ${( - torrent.uploaded / - (1024 * 1024) - ).toFixed(2)} MB +
Uploaded: ${(torrent.uploaded / (1024 * 1024)).toFixed(2)} MB
Ratio: ${torrent.ratio.toFixed(2)}`; - return; - } - - downloadResult.innerHTML = "File not found. Please check the mnemonic."; - }, 1000); + }, 1000); + }); } catch (error) { - console.error("Error downloading file:", error); - alert("Failed to download file. Please check the mnemonic and try again."); + console.error("Error downloading files:", error); + alert("Failed to download files. Please check the mnemonic and try again."); } finally { downloadButton.disabled = false; } @@ -208,13 +189,13 @@ document.addEventListener("DOMContentLoaded", () => { document .getElementById("uploadButton") - .addEventListener("click", () => uploadFile(trackerUrl)); + .addEventListener("click", () => uploadFiles(trackerUrl)); document .getElementById("copyButton") .addEventListener("click", copyToClipboard); document .getElementById("downloadButton") - .addEventListener("click", () => downloadFile(trackerUrl)); + .addEventListener("click", () => downloadFiles(trackerUrl)); if (mnemonic) { const mnemonicInput = document.getElementById("mnemonicInput"); diff --git a/views/index.ejs b/views/index.ejs index 3ad294f..15bb7a8 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -17,27 +17,23 @@

Transfer.coffee is a simple way to share files between devices. Just - select a file and share the generated URL with the recipient. The - recipient can then download the file by entering the mnemonic. + select one or multiple files and share the generated URL with the + recipient. The recipient can then download the file by entering the + mnemonic.

The files are shared using WebTorrent, a peer-to-peer file sharing - protocol. This means that the file is not stored on a central server + protocol. This means that the files are not stored on a central server and is instead shared directly between the sender and the recipient.

Share File

- +
- +

Receive File

@@ -70,7 +66,12 @@

- +