feat: add initial WebTorrent-based file transfer app
- Initialize new Express application for file transfer using WebTorrent. - Set up routes for generating and retrieving mnemonics and info hashes. - Implement custom BitTorrent tracker server with WebSockets. - Add `.gitignore` to exclude `node_modules/`. - Create `index.ejs` for front-end with file upload and download functionalities. - Initialize `package.json` and `package-lock.json` with necessary dependencies.
This commit is contained in:
commit
a53fabdec1
6 changed files with 3926 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules/
|
34
app.js
Normal file
34
app.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import bip39 from 'bip39';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const trackerUrl = process.env.TRACKER_URL || 'ws://localhost:8106';
|
||||||
|
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.render('index', { trackerUrl });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/generate-mnemonic/:infoHash', (req, res) => {
|
||||||
|
const infoHash = req.params.infoHash;
|
||||||
|
const mnemonic = bip39.entropyToMnemonic(infoHash);
|
||||||
|
res.json({ mnemonic });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/get-infohash/:mnemonic', (req, res) => {
|
||||||
|
const mnemonic = req.params.mnemonic;
|
||||||
|
const infoHash = bip39.mnemonicToEntropy(mnemonic);
|
||||||
|
res.json({ infoHash });
|
||||||
|
});
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 8105;
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Server is running on port ${PORT}`);
|
||||||
|
});
|
3660
package-lock.json
generated
Normal file
3660
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
package.json
Normal file
27
package.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "transfer.coffee",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A WebTorrent-based file transfer application",
|
||||||
|
"main": "app.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node app.js",
|
||||||
|
"tracker": "node tracker.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.private.coffee/PrivateCoffee/transfer.coffee"
|
||||||
|
},
|
||||||
|
"author": "Private.coffee Team",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bip39": "^3.1.0",
|
||||||
|
"bittorrent-tracker": "^11.1.0",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"socket.io": "^4.7.5",
|
||||||
|
"webtorrent": "^2.4.1"
|
||||||
|
}
|
||||||
|
}
|
25
tracker.js
Normal file
25
tracker.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Server } from 'bittorrent-tracker';
|
||||||
|
|
||||||
|
const PORT = process.env.TRACKER_PORT || 8106;
|
||||||
|
const HOST = process.env.TRACKER_HOST || 'localhost';
|
||||||
|
|
||||||
|
const server = new Server({
|
||||||
|
udp: true,
|
||||||
|
http: true,
|
||||||
|
ws: true,
|
||||||
|
stats: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('error', (err) => {
|
||||||
|
console.error(`Error: ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('warning', (err) => {
|
||||||
|
console.warn(`Warning: ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('listening', () => {
|
||||||
|
console.log(`Tracker is listening on http://{HOST}:{PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT);
|
179
views/index.ejs
Normal file
179
views/index.ejs
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Transfer.coffee</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
input[type="file"],
|
||||||
|
input[type="text"],
|
||||||
|
input[type="url"] {
|
||||||
|
width: calc(100% - 22px);
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
width: 0;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #28a745;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.result {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/js/webtorrent.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Transfer.coffee</h1>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Upload File</h2>
|
||||||
|
<input type="file" id="fileInput" />
|
||||||
|
<button onclick="uploadFile()">Upload</button>
|
||||||
|
<div class="progress" id="uploadProgress">
|
||||||
|
<div class="progress-bar" id="uploadProgressBar">0%</div>
|
||||||
|
</div>
|
||||||
|
<div class="result" id="uploadResult"></div>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Download File</h2>
|
||||||
|
<input type="text" id="mnemonicInput" placeholder="Enter mnemonic" />
|
||||||
|
<button onclick="downloadFile()">Download</button>
|
||||||
|
<div class="progress" id="downloadProgress">
|
||||||
|
<div class="progress-bar" id="downloadProgressBar">0%</div>
|
||||||
|
</div>
|
||||||
|
<div class="result" id="downloadResult"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const client = new WebTorrent();
|
||||||
|
const trackerUrl = "<%= trackerUrl %>";
|
||||||
|
|
||||||
|
function uploadFile() {
|
||||||
|
const fileInput = document.getElementById("fileInput");
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
const uploadProgressBar = document.getElementById("uploadProgressBar");
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
alert("Please select a file to upload.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
announce: [trackerUrl],
|
||||||
|
};
|
||||||
|
|
||||||
|
client.seed(file, opts, (torrent) => {
|
||||||
|
fetch(`/generate-mnemonic/${torrent.infoHash}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
const uploadResult = document.getElementById("uploadResult");
|
||||||
|
uploadResult.innerHTML = `File uploaded. Share this mnemonic: <strong>${data.mnemonic}</strong>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
torrent.on("upload", () => {
|
||||||
|
const progress = Math.round(
|
||||||
|
(torrent.uploaded / torrent.length) * 100
|
||||||
|
);
|
||||||
|
uploadProgressBar.style.width = `${progress}%`;
|
||||||
|
uploadProgressBar.textContent = `${progress}%`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFile() {
|
||||||
|
const mnemonicInput = document.getElementById("mnemonicInput").value;
|
||||||
|
const downloadProgressBar = document.getElementById(
|
||||||
|
"downloadProgressBar"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mnemonicInput) {
|
||||||
|
alert("Please enter a mnemonic.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/get-infohash/${mnemonicInput}`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
const torrentId = data.infoHash;
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
announce: [trackerUrl],
|
||||||
|
};
|
||||||
|
|
||||||
|
client.add(torrentId, opts, (torrent) => {
|
||||||
|
torrent.files[0].getBlob((err, blob) => {
|
||||||
|
if (err) {
|
||||||
|
const downloadResult =
|
||||||
|
document.getElementById("downloadResult");
|
||||||
|
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();
|
||||||
|
|
||||||
|
const downloadResult =
|
||||||
|
document.getElementById("downloadResult");
|
||||||
|
downloadResult.innerHTML = `File downloaded: <strong>${torrent.files[0].name}</strong>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
torrent.on("download", () => {
|
||||||
|
const progress = Math.round(
|
||||||
|
(torrent.downloaded / torrent.length) * 100
|
||||||
|
);
|
||||||
|
downloadProgressBar.style.width = `${progress}%`;
|
||||||
|
downloadProgressBar.textContent = `${progress}%`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue