forked from PrivateCoffee/transfer.coffee
feat: improve security and error handling in app
- Added `helmet` middleware for enhanced security with CSP. - Integrated `dotenv` for configuration management. - Added validation and error handling for mnemonic and infoHash. - Improved error handling in TURN credentials generation. - Enhanced notification and progress feedback for file sharing. - Added tracker server config validation and error handling. - Updated dependencies to include `helmet` and `dotenv`. These changes improve the app's security, robustness, and user experience.
This commit is contained in:
parent
f87ec52173
commit
2394a9f6ee
6 changed files with 261 additions and 153 deletions
75
app.js
75
app.js
|
@ -3,6 +3,10 @@ import path from "path";
|
|||
import { fileURLToPath } from "url";
|
||||
import bip39 from "bip39";
|
||||
import crypto from "crypto";
|
||||
import helmet from "helmet";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
@ -17,16 +21,51 @@ const turnTTL = 86400;
|
|||
app.set("view engine", "ejs");
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
||||
app.use(helmet());
|
||||
app.use(
|
||||
helmet.contentSecurityPolicy({
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'"],
|
||||
connectSrc: ["'self'", trackerUrl],
|
||||
imgSrc: ["'self'", "data:"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
const isValidInfoHash = (infoHash) => /^[0-9a-fA-F]{40}$/.test(infoHash);
|
||||
|
||||
app.get("/generate-mnemonic/:infoHash", (req, res) => {
|
||||
const infoHash = req.params.infoHash;
|
||||
const mnemonic = bip39.entropyToMnemonic(infoHash);
|
||||
res.json({ mnemonic });
|
||||
|
||||
if (!isValidInfoHash(infoHash)) {
|
||||
return res.status(400).json({ error: "Invalid infoHash" });
|
||||
}
|
||||
|
||||
try {
|
||||
const mnemonic = bip39.entropyToMnemonic(infoHash);
|
||||
res.json({ mnemonic });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to generate mnemonic" });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/get-infohash/:mnemonic", (req, res) => {
|
||||
const mnemonic = req.params.mnemonic;
|
||||
const infoHash = bip39.mnemonicToEntropy(mnemonic);
|
||||
res.json({ infoHash });
|
||||
|
||||
if (!bip39.validateMnemonic(mnemonic)) {
|
||||
return res.status(400).json({ error: "Invalid mnemonic" });
|
||||
}
|
||||
|
||||
try {
|
||||
const infoHash = bip39.mnemonicToEntropy(mnemonic);
|
||||
res.json({ infoHash });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to get infoHash" });
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/turn-credentials", (req, res) => {
|
||||
|
@ -37,24 +76,30 @@ app.get("/turn-credentials", (req, res) => {
|
|||
}
|
||||
|
||||
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");
|
||||
try {
|
||||
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,
|
||||
});
|
||||
iceServers.push({
|
||||
urls: turnServerUrl,
|
||||
username: username,
|
||||
credential: credential,
|
||||
});
|
||||
} catch (error) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Failed to generate TURN credentials" });
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ iceServers });
|
||||
});
|
||||
|
||||
app.get("/:mnemonic?", (req, res) => {
|
||||
var mnemonic = req.params.mnemonic || "";
|
||||
let mnemonic = req.params.mnemonic || "";
|
||||
|
||||
if (mnemonic) {
|
||||
mnemonic = mnemonic.replaceAll(".", " ").trim();
|
||||
|
|
43
package-lock.json
generated
43
package-lock.json
generated
|
@ -11,8 +11,10 @@
|
|||
"dependencies": {
|
||||
"bip39": "^3.1.0",
|
||||
"bittorrent-tracker": "^11.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2"
|
||||
"express": "^4.19.2",
|
||||
"helmet": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
|
@ -297,11 +299,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/bittorrent-tracker/node_modules/ip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
|
||||
},
|
||||
"node_modules/bittorrent-tracker/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -624,6 +621,18 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -950,6 +959,15 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/helmet": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
|
||||
"integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
|
@ -1005,6 +1023,12 @@
|
|||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
|
||||
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
|
||||
|
@ -1873,9 +1897,10 @@
|
|||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
"dependencies": {
|
||||
"bip39": "^3.1.0",
|
||||
"bittorrent-tracker": "^11.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.19.2"
|
||||
"express": "^4.19.2",
|
||||
"helmet": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
const client = new WebTorrent();
|
||||
|
||||
async function getRTCIceServers() {
|
||||
const response = await fetch("/turn-credentials");
|
||||
const data = await response.json();
|
||||
return data.iceServers;
|
||||
try {
|
||||
const response = await fetch("/turn-credentials");
|
||||
if (!response.ok) throw new Error("Failed to fetch TURN credentials");
|
||||
const data = await response.json();
|
||||
return data.iceServers;
|
||||
} catch (error) {
|
||||
console.error("Error getting ICE servers:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadFile() {
|
||||
async function uploadFile(trackerUrl) {
|
||||
const fileInput = document.getElementById("fileInput");
|
||||
const file = fileInput.files[0];
|
||||
const uploadStats = document.getElementById("uploadStats");
|
||||
|
@ -24,19 +29,21 @@ async function uploadFile() {
|
|||
uploadSection.style.display = "block";
|
||||
uploadButton.disabled = true;
|
||||
|
||||
const rtcConfig = {
|
||||
iceServers: await getRTCIceServers(),
|
||||
};
|
||||
try {
|
||||
const rtcConfig = {
|
||||
iceServers: await getRTCIceServers(),
|
||||
};
|
||||
|
||||
const opts = {
|
||||
announce: [trackerUrl],
|
||||
rtcConfig: rtcConfig,
|
||||
};
|
||||
const opts = {
|
||||
announce: [trackerUrl],
|
||||
rtcConfig: rtcConfig,
|
||||
};
|
||||
|
||||
client.seed(file, opts, async (torrent) => {
|
||||
fetch(`/generate-mnemonic/${torrent.infoHash}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
client.seed(file, 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
|
||||
|
@ -46,43 +53,45 @@ async function uploadFile() {
|
|||
<br>Note that the file will be available for download only as long as you keep this page open.`;
|
||||
copyButton.style.display = "inline-block";
|
||||
copyButton.setAttribute("data-url", downloadUrl);
|
||||
});
|
||||
|
||||
let totalPeers = 0;
|
||||
const seenPeers = new Set();
|
||||
|
||||
setInterval(async () => {
|
||||
for (const wire of torrent.wires) {
|
||||
let peerIdHash;
|
||||
try {
|
||||
peerIdHash = await sha256(wire.peerId);
|
||||
} catch (e) {
|
||||
peerIdHash = wire.peerId;
|
||||
}
|
||||
|
||||
if (!seenPeers.has(peerIdHash)) {
|
||||
seenPeers.add(peerIdHash);
|
||||
totalPeers += 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error generating mnemonic:", error);
|
||||
alert("Failed to generate mnemonic. Please try again.");
|
||||
}
|
||||
|
||||
const uploaded = (torrent.uploaded / (1024 * 1024)).toFixed(2);
|
||||
uploadStats.innerHTML = `Uploaded: ${uploaded} MB to ${totalPeers} peer(s)`;
|
||||
}, 1000);
|
||||
});
|
||||
let totalPeers = 0;
|
||||
const seenPeers = new Set();
|
||||
|
||||
setInterval(async () => {
|
||||
for (const wire of torrent.wires) {
|
||||
let peerIdHash;
|
||||
try {
|
||||
peerIdHash = await sha256(wire.peerId);
|
||||
} catch (e) {
|
||||
peerIdHash = wire.peerId;
|
||||
}
|
||||
|
||||
if (!seenPeers.has(peerIdHash)) {
|
||||
seenPeers.add(peerIdHash);
|
||||
totalPeers += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const uploaded = (torrent.uploaded / (1024 * 1024)).toFixed(2);
|
||||
uploadStats.innerHTML = `Uploaded: ${uploaded} MB to ${totalPeers} peer(s)`;
|
||||
}, 1000);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error sharing file:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
const copyButton = document.getElementById("copyButton");
|
||||
const url = copyButton.getAttribute("data-url");
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() => {
|
||||
alert("URL copied to clipboard");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy: ", err);
|
||||
});
|
||||
navigator.clipboard.writeText(url).catch((err) => {
|
||||
console.error("Failed to copy: ", err);
|
||||
alert("Failed to copy URL to clipboard. Please try again.");
|
||||
});
|
||||
}
|
||||
|
||||
async function sha256(str) {
|
||||
|
@ -93,10 +102,11 @@ async function sha256(str) {
|
|||
.join("");
|
||||
}
|
||||
|
||||
async function downloadFile() {
|
||||
async function downloadFile(trackerUrl) {
|
||||
const mnemonicInput = document.getElementById("mnemonicInput").value;
|
||||
const downloadProgressBar = document.getElementById("downloadProgressBar");
|
||||
const downloadButton = document.getElementById("downloadButton");
|
||||
const downloadResult = document.getElementById("downloadResult");
|
||||
|
||||
if (!mnemonicInput) {
|
||||
alert("Please enter a mnemonic.");
|
||||
|
@ -109,89 +119,105 @@ async function downloadFile() {
|
|||
|
||||
downloadResult.innerHTML = "Preparing incoming file transfer, please wait...";
|
||||
|
||||
const rtcConfig = {
|
||||
iceServers: await getRTCIceServers(),
|
||||
};
|
||||
try {
|
||||
const rtcConfig = {
|
||||
iceServers: await getRTCIceServers(),
|
||||
};
|
||||
|
||||
fetch(`/get-infohash/${mnemonicInput}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const torrentId = data.infoHash;
|
||||
const response = await fetch(`/get-infohash/${mnemonicInput}`);
|
||||
if (!response.ok) throw new Error("Failed to get infoHash");
|
||||
const data = await response.json();
|
||||
const torrentId = data.infoHash;
|
||||
|
||||
const opts = {
|
||||
announce: [trackerUrl],
|
||||
rtcConfig: rtcConfig,
|
||||
};
|
||||
const opts = {
|
||||
announce: [trackerUrl],
|
||||
rtcConfig: rtcConfig,
|
||||
};
|
||||
|
||||
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}%`;
|
||||
});
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
const downloadResult = document.getElementById("downloadResult");
|
||||
|
||||
if (client.get(torrentId)) {
|
||||
const torrent = client.get(torrentId);
|
||||
const progress = Math.round(
|
||||
(torrent.downloaded / torrent.length) * 100
|
||||
);
|
||||
downloadResult.innerHTML = `Downloading:
|
||||
<strong>${torrent.files[0].name}</strong>
|
||||
<br>
|
||||
<br><strong>Status:</strong> ${
|
||||
torrent.done ? "Completed, seeding" : "Downloading"
|
||||
}
|
||||
<br><strong>Peers:</strong> ${torrent.numPeers}
|
||||
<br>
|
||||
<br><strong>Downloaded:</strong> ${(
|
||||
torrent.downloaded /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)} MB / ${(torrent.length / (1024 * 1024)).toFixed(
|
||||
2
|
||||
)} MB (${progress}%)
|
||||
<br><strong>Speed:</strong> ${(
|
||||
torrent.downloadSpeed /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)} MB/s
|
||||
<br><strong>ETA:</strong> ${torrent.timeRemaining} seconds
|
||||
<br>
|
||||
<br><strong>Uploaded:</strong> ${(
|
||||
torrent.uploaded /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)} MB
|
||||
<br><strong>Ratio:</strong> ${torrent.ratio.toFixed(2)}`;
|
||||
client.add(torrentId, opts, (torrent) => {
|
||||
torrent.files[0].getBlob((err, blob) => {
|
||||
if (err) {
|
||||
downloadResult.innerHTML = `Error: ${err.message}`;
|
||||
return;
|
||||
}
|
||||
|
||||
downloadResult.innerHTML = "File not found. Please check the mnemonic.";
|
||||
}, 1000);
|
||||
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: <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}%`;
|
||||
});
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
if (client.get(torrentId)) {
|
||||
const torrent = client.get(torrentId);
|
||||
const progress = Math.round(
|
||||
(torrent.downloaded / torrent.length) * 100
|
||||
);
|
||||
downloadResult.innerHTML = `Downloading:
|
||||
<strong>${torrent.files[0].name}</strong>
|
||||
<br>
|
||||
<br><strong>Status:</strong> ${
|
||||
torrent.done ? "Completed, seeding" : "Downloading"
|
||||
}
|
||||
<br><strong>Peers:</strong> ${torrent.numPeers}
|
||||
<br>
|
||||
<br><strong>Downloaded:</strong> ${(
|
||||
torrent.downloaded /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)} MB / ${(torrent.length / (1024 * 1024)).toFixed(
|
||||
2
|
||||
)} MB (${progress}%)
|
||||
<br><strong>Speed:</strong> ${(
|
||||
torrent.downloadSpeed /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)} MB/s
|
||||
<br><strong>ETA:</strong> ${torrent.timeRemaining} seconds
|
||||
<br>
|
||||
<br><strong>Uploaded:</strong> ${(
|
||||
torrent.uploaded /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)} MB
|
||||
<br><strong>Ratio:</strong> ${torrent.ratio.toFixed(2)}`;
|
||||
return;
|
||||
}
|
||||
|
||||
downloadResult.innerHTML = "File not found. Please check the mnemonic.";
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error("Error downloading file:", error);
|
||||
alert("Failed to download file. Please check the mnemonic and try again.");
|
||||
} finally {
|
||||
downloadButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const configElement = document.getElementById("config");
|
||||
const trackerUrl = configElement.getAttribute("data-tracker-url");
|
||||
const mnemonic = configElement.getAttribute("data-mnemonic");
|
||||
|
||||
document
|
||||
.getElementById("uploadButton")
|
||||
.addEventListener("click", () => uploadFile(trackerUrl));
|
||||
document
|
||||
.getElementById("copyButton")
|
||||
.addEventListener("click", copyToClipboard);
|
||||
document
|
||||
.getElementById("downloadButton")
|
||||
.addEventListener("click", () => downloadFile(trackerUrl));
|
||||
|
||||
if (mnemonic) {
|
||||
const mnemonicInput = document.getElementById("mnemonicInput");
|
||||
const downloadButton = document.getElementById("downloadButton");
|
||||
|
|
18
tracker.js
18
tracker.js
|
@ -1,8 +1,16 @@
|
|||
import { Server } from "bittorrent-tracker";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const PORT = process.env.TRACKER_PORT || 8106;
|
||||
const HOST = process.env.TRACKER_HOST || "localhost";
|
||||
|
||||
const port = Number(PORT);
|
||||
if (isNaN(port) || port <= 0) {
|
||||
throw new Error("Invalid TRACKER_PORT value. It must be a positive number.");
|
||||
}
|
||||
|
||||
const server = new Server({
|
||||
udp: false,
|
||||
http: false,
|
||||
|
@ -19,7 +27,13 @@ server.on("warning", (err) => {
|
|||
});
|
||||
|
||||
server.on("listening", () => {
|
||||
console.log(`Tracker is listening on http://${HOST}:${PORT}`);
|
||||
console.log(`Tracker is listening on ws://${HOST}:${PORT}`);
|
||||
});
|
||||
|
||||
server.listen(PORT);
|
||||
try {
|
||||
server.listen(port, HOST, () => {
|
||||
console.log(`Tracker server started on ws://${HOST}:${PORT}`);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Failed to start tracker server: ${err.message}`);
|
||||
}
|
|
@ -29,13 +29,12 @@
|
|||
<div class="section" id="uploadSection">
|
||||
<h2>Share File</h2>
|
||||
<input type="file" id="fileInput" />
|
||||
<button id="uploadButton" onclick="uploadFile()">Share</button>
|
||||
<button id="uploadButton">Share</button>
|
||||
<div class="result" id="uploadResult"></div>
|
||||
<div class="result" id="uploadStats"></div>
|
||||
<button
|
||||
id="copyButton"
|
||||
style="display: none"
|
||||
onclick="copyToClipboard()"
|
||||
>
|
||||
Copy URL
|
||||
</button>
|
||||
|
@ -43,7 +42,7 @@
|
|||
<div class="section" id="downloadSection">
|
||||
<h2>Receive File</h2>
|
||||
<input type="text" id="mnemonicInput" placeholder="Enter mnemonic" />
|
||||
<button id="downloadButton" onclick="downloadFile()">Receive</button>
|
||||
<button id="downloadButton">Receive</button>
|
||||
<div class="progress" id="downloadProgress">
|
||||
<div class="progress-bar" id="downloadProgressBar">0%</div>
|
||||
</div>
|
||||
|
@ -71,10 +70,7 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const trackerUrl = "<%= trackerUrl %>";
|
||||
const mnemonic = "<%= mnemonic %>";
|
||||
</script>
|
||||
<div id="config" data-tracker-url="<%= trackerUrl %>" data-mnemonic="<%= mnemonic %>" style="display: none;"></div>
|
||||
<script src="/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue