feat: add STUN/TURN server support and environment config

- Extended .gitignore to exclude .env files containing sensitive info.
- Imported `crypto` module and added route to generate TURN credentials.
- Enhanced upload/download functionality to use STUN/TURN servers.
- Improved UI text for upload/download scenarios for clarity.
- Added fetching of TURN credentials in the client to enable P2P connections.

These changes improve file sharing reliability by providing fallbacks for network traversal.
This commit is contained in:
Kumi 2024-06-14 18:32:51 +02:00
parent 1a1ddbeeb6
commit eb06ffbfd8
Signed by: kumi
GPG key ID: ECBCC9082395383F
4 changed files with 54 additions and 8 deletions

3
.gitignore vendored
View file

@ -1 +1,2 @@
node_modules/ node_modules/
.env

29
app.js
View file

@ -2,12 +2,17 @@ import express from 'express';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import bip39 from 'bip39'; import bip39 from 'bip39';
import crypto from 'crypto';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const app = express(); const app = express();
const trackerUrl = process.env.TRACKER_URL || 'ws://localhost:8106'; const trackerUrl = process.env.TRACKER_URL || 'ws://localhost:8106';
const stunServerUrl = process.env.STUN_SERVER_URL;
const turnServerUrl = process.env.TURN_SERVER_URL;
const turnSecret = process.env.TURN_SECRET;
const turnTTL = 86400;
app.set('view engine', 'ejs'); app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
@ -28,6 +33,30 @@ app.get('/get-infohash/:mnemonic', (req, res) => {
res.json({ infoHash }); res.json({ infoHash });
}); });
app.get('/turn-credentials', (req, res) => {
const iceServers = [];
if (stunServerUrl) {
iceServers.push({ urls: stunServerUrl });
}
if (turnServerUrl && turnSecret) {
const unixTimeStamp = Math.floor(Date.now() / 1000) + turnTTL;
const username = `${unixTimeStamp}:transfercoffee`;
const hmac = crypto.createHmac('sha1', turnSecret);
hmac.update(username);
const credential = hmac.digest('base64');
iceServers.push({
urls: turnServerUrl,
username: username,
credential: credential
});
}
res.json({ iceServers });
});
const PORT = process.env.PORT || 8105; const PORT = process.env.PORT || 8105;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`); console.log(`Server is running on port ${PORT}`);

View file

@ -1,6 +1,13 @@
const client = new WebTorrent(); const client = new WebTorrent();
const trackerUrl = "<%= trackerUrl %>";
function uploadFile() { async function getRTCIceServers() {
const response = await fetch("/turn-credentials");
const data = await response.json();
return data.iceServers;
}
async function uploadFile() {
const fileInput = document.getElementById("fileInput"); const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0]; const file = fileInput.files[0];
const uploadProgressBar = document.getElementById("uploadProgressBar"); const uploadProgressBar = document.getElementById("uploadProgressBar");
@ -10,8 +17,13 @@ function uploadFile() {
return; return;
} }
const rtcConfig = {
iceServers: await getRTCIceServers(),
};
const opts = { const opts = {
announce: [trackerUrl], announce: [trackerUrl],
rtcConfig: rtcConfig,
}; };
client.seed(file, opts, (torrent) => { client.seed(file, opts, (torrent) => {
@ -19,9 +31,8 @@ function uploadFile() {
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
const uploadResult = document.getElementById("uploadResult"); const uploadResult = document.getElementById("uploadResult");
uploadResult.innerHTML = `Started sharing file. Share this mnemonic: <strong>${data.mnemonic}</strong> uploadResult.innerHTML = `<p>Sharing your file. Share this mnemonic: <strong>${data.mnemonic}</strong></p>
<br>Note that the file will be available for download as long as this page is open. <p>The file will be available for download as long as you keep this page open.</p>`;
`;
}); });
torrent.on("upload", () => { torrent.on("upload", () => {
@ -32,7 +43,7 @@ function uploadFile() {
}); });
} }
function downloadFile() { async function downloadFile() {
const mnemonicInput = document.getElementById("mnemonicInput").value; const mnemonicInput = document.getElementById("mnemonicInput").value;
const downloadProgressBar = document.getElementById("downloadProgressBar"); const downloadProgressBar = document.getElementById("downloadProgressBar");
@ -41,6 +52,10 @@ function downloadFile() {
return; return;
} }
const rtcConfig = {
iceServers: await getRTCIceServers(),
};
fetch(`/get-infohash/${mnemonicInput}`) fetch(`/get-infohash/${mnemonicInput}`)
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
@ -48,6 +63,7 @@ function downloadFile() {
const opts = { const opts = {
announce: [trackerUrl], announce: [trackerUrl],
rtcConfig: rtcConfig,
}; };
client.add(torrentId, opts, (torrent) => { client.add(torrentId, opts, (torrent) => {

View file

@ -18,9 +18,9 @@
<div class="result" id="uploadResult"></div> <div class="result" id="uploadResult"></div>
</div> </div>
<div class="section"> <div class="section">
<h2>Download File</h2> <h2>Receive File</h2>
<input type="text" id="mnemonicInput" placeholder="Enter mnemonic" /> <input type="text" id="mnemonicInput" placeholder="Enter mnemonic" />
<button onclick="downloadFile()">Download</button> <button onclick="downloadFile()">Receive</button>
<div class="progress" id="downloadProgress"> <div class="progress" id="downloadProgress">
<div class="progress-bar" id="downloadProgressBar">0%</div> <div class="progress-bar" id="downloadProgressBar">0%</div>
</div> </div>