Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion examples/test-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ async function startClient() {
const client = new FileClient({
peers: ['http://nostalgiagame.go.ro:30878/gun'], // Connect to deployed relay
namespace: 'dig-nat-tools-test', // Use same namespace as host
timeout: 600000 // 10 minute timeout (600 seconds) - increased from 5 minutes
timeout: 600000, // 10 minute timeout (600 seconds) - increased from 5 minutes

// Custom WebTorrent trackers (optional) - replace unreliable public trackers
trackers: [
'ws://nostalgiagame.go.ro:8000', // Your custom WebSocket tracker
'http://nostalgiagame.go.ro:8000/announce' // Your custom HTTP tracker
]
});

try {
Expand Down
10 changes: 9 additions & 1 deletion examples/test-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ async function runExample() {
gun: {
peers: ['http://nostalgiagame.go.ro:30878/gun'],
namespace: 'dig-nat-tools-test'
}
},

// Custom WebTorrent trackers (optional) - uncomment to use your own tracker
trackers: [
'ws://localhost:8000', // Your custom WebSocket tracker
'http://localhost:8000/announce', // Your custom HTTP tracker
'wss://tracker.openwebtorrent.com', // Backup reliable tracker
'udp://tracker.opentrackr.org:1337' // Backup reliable tracker
]
});

// Declare testFiles in function scope so it's accessible everywhere
Expand Down
110 changes: 110 additions & 0 deletions examples/tracker-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// tracker-server.js - Custom WebTorrent tracker server
import { Server } from 'bittorrent-tracker';

const PORT = process.env.TRACKER_PORT || 8000;
const HOST = process.env.TRACKER_HOST || '0.0.0.0';

console.log('🚀 Starting custom WebTorrent tracker server...');

const server = new Server({
udp: true, // Enable UDP tracker
http: true, // Enable HTTP tracker
ws: true, // Enable WebSocket tracker (for web clients)
stats: true, // Enable stats endpoint
filter: function (infoHash, params, cb) {
// Optional: filter which torrents are allowed
// For now, allow all torrents
cb(null, true);
}
});

// Error handling
server.on('error', (err) => {
console.error('❌ Tracker server error:', err);
});

server.on('warning', (err) => {
console.warn('⚠️ Tracker server warning:', err);
});

server.on('listening', () => {
console.log('✅ Custom WebTorrent tracker server running!');
console.log(`📊 Tracker endpoints:`);
console.log(` - HTTP: http://${HOST}:${PORT}/announce`);
console.log(` - WebSocket: ws://${HOST}:${PORT}`);
console.log(` - UDP: udp://${HOST}:${PORT}`);
console.log(` - Stats: http://${HOST}:${PORT}/stats`);
console.log('');
console.log('🔧 To use this tracker, configure your host/client with:');
console.log(` trackers: ['ws://${HOST}:${PORT}', 'http://${HOST}:${PORT}/announce']`);
console.log('');
console.log('Press Ctrl+C to stop');
});

// Start the server
server.listen(PORT, HOST);

// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Received interrupt signal, shutting down tracker...');
server.close(() => {
console.log('✅ Tracker server closed');
process.exit(0);
});
});

// Enhanced peer activity logging
server.on('start', (addr, params) => {
console.log(`📥 [${new Date().toLocaleTimeString()}] Peer STARTED: ${addr.address}:${addr.port}`);
console.log(` Info Hash: ${params.info_hash ? params.info_hash.toString('hex') : 'unknown'}`);
console.log(` Client: ${params.peer_id ? params.peer_id.toString() : 'unknown'}`);
});

server.on('complete', (addr, params) => {
console.log(`✅ [${new Date().toLocaleTimeString()}] Peer COMPLETED: ${addr.address}:${addr.port}`);
console.log(` Info Hash: ${params.info_hash ? params.info_hash.toString('hex') : 'unknown'}`);
});

server.on('update', (addr, params) => {
console.log(`🔄 [${new Date().toLocaleTimeString()}] Peer UPDATED: ${addr.address}:${addr.port}`);
console.log(` Downloaded: ${params.downloaded || 0} bytes`);
console.log(` Uploaded: ${params.uploaded || 0} bytes`);
console.log(` Left: ${params.left || 0} bytes`);
});

server.on('stop', (addr, params) => {
console.log(`🛑 [${new Date().toLocaleTimeString()}] Peer STOPPED: ${addr.address}:${addr.port}`);
console.log(` Info Hash: ${params.info_hash ? params.info_hash.toString('hex') : 'unknown'}`);
});

// Log HTTP requests to the tracker
server.on('request', (params, cb) => {
console.log(`🌐 [${new Date().toLocaleTimeString()}] HTTP Request:`);
console.log(` Event: ${params.event || 'none'}`);
console.log(` Info Hash: ${params.info_hash ? params.info_hash.toString('hex') : 'unknown'}`);
console.log(` Peer ID: ${params.peer_id ? params.peer_id.toString() : 'unknown'}`);
console.log(` IP: ${params.ip || 'unknown'}`);
console.log(` Port: ${params.port || 'unknown'}`);
});

// Log WebSocket connections
server.on('connection', (socket) => {
console.log(`🔌 [${new Date().toLocaleTimeString()}] WebSocket connection from: ${socket._socket ? socket._socket.remoteAddress : 'unknown'}`);

socket.on('close', () => {
console.log(`🔌 [${new Date().toLocaleTimeString()}] WebSocket disconnected: ${socket._socket ? socket._socket.remoteAddress : 'unknown'}`);
});
});

// Periodic stats logging
setInterval(() => {
const stats = server.getSwarm();
const totalTorrents = Object.keys(stats).length;
let totalPeers = 0;

Object.values(stats).forEach(swarm => {
totalPeers += swarm.complete + swarm.incomplete;
});

console.log(`📊 Tracker stats: ${totalTorrents} torrents, ${totalPeers} peers`);
}, 60000); // Every minute
8 changes: 2 additions & 6 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default {
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
transform: {
'^.+\\.ts$': ['ts-jest', {
useESM: true,
tsconfig: {
types: ['jest', 'node'],
module: 'esnext'
Expand All @@ -23,10 +24,5 @@ export default {
transformIgnorePatterns: [
'node_modules/(?!(public-ip|gun)/)'
],
extensionsToTreatAsEsm: ['.ts'],
globals: {
'ts-jest': {
useESM: true
}
}
extensionsToTreatAsEsm: ['.ts']
};
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"dependencies": {
"@roamhq/wrtc": "^0.9.0",
"@types/webtorrent": "^0.110.1",
"bittorrent-tracker": "^11.2.2",
"express": "^4.18.2",
"gun": "^0.2020.1241",
"nat-upnp": "^1.1.1",
Expand Down
30 changes: 27 additions & 3 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
namespace?: string; // Gun.js namespace
timeout?: number; // Download timeout
logger?: Logger; // Optional logger for debug output
trackers?: string[]; // Custom WebTorrent trackers
}

export class FileClient implements IFileClient {
Expand All @@ -38,6 +39,7 @@
peers: options.peers || ["http://nostalgiagame.go.ro:30878/gun"],
namespace: options.namespace || "dig-nat-tools",
timeout: options.timeout || 30000,
trackers: options.trackers, // Store custom trackers
};

// Create a default logger that only shows warnings and errors if none provided
Expand Down Expand Up @@ -271,13 +273,24 @@
);

try {
this.webTorrentClient = new WebTorrent();
// Initialize WebTorrent with custom trackers if provided, otherwise use defaults
if (this.options.trackers && this.options.trackers.length > 0) {
this.webTorrentClient = new WebTorrent({
tracker: {
announce: this.options.trackers
}
});
} else {
// Use default WebTorrent configuration (includes built-in reliable trackers)
this.webTorrentClient = new WebTorrent();
}

// Log client status (using safe property access)
this.logger.debug(`🔧 WebTorrent client created:`, {
activeTorrents: this.webTorrentClient.torrents.length,
clientType: 'WebTorrent',
initialized: !!this.webTorrentClient
initialized: !!this.webTorrentClient,
trackers: this.options.trackers || 'default reliable trackers'
});

} catch (error) {
Expand All @@ -301,7 +314,18 @@

let torrent: WebTorrent.Torrent | null = null;
try {
torrent = this.webTorrentClient!.add(magnetUri);
// If custom trackers are configured, override the magnet URI trackers
if (this.options.trackers && this.options.trackers.length > 0) {

Check warning on line 318 in src/client.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
// Add torrent with custom tracker override
torrent = this.webTorrentClient!.add(magnetUri, {
announce: this.options.trackers
});

Check warning on line 322 in src/client.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
this.logger.debug(`🔧 Using custom trackers for torrent: ${this.options.trackers.join(', ')}`);

Check warning on line 323 in src/client.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {
// Use default behavior (trackers from magnet URI)
torrent = this.webTorrentClient!.add(magnetUri);
this.logger.debug(`🔧 Using trackers from magnet URI`);
}

Check warning on line 328 in src/client.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
} catch (error) {
this.logger.error("❌ Failed to add torrent:", {
...this.serializeError(error),
Expand Down
29 changes: 27 additions & 2 deletions src/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
peers: string[]; // Gun.js peer URLs
namespace?: string; // Registry namespace
};
trackers?: string[]; // Custom WebTorrent trackers
}

export class FileHost implements IFileHost {
Expand Down Expand Up @@ -335,7 +336,20 @@
) {
try {
this.logger.debug(`🔄 Initializing WebTorrent client...`);
this.webTorrentClient = new WebTorrent();

// Initialize WebTorrent with custom trackers if provided, otherwise use defaults
if (this.options.trackers && this.options.trackers.length > 0) {
this.webTorrentClient = new WebTorrent({
tracker: {
announce: this.options.trackers
}
});
this.logger.debug(`🔧 Using custom trackers: ${this.options.trackers.join(', ')}`);
} else {
// Use default WebTorrent configuration (includes built-in reliable trackers)
this.webTorrentClient = new WebTorrent();
this.logger.debug(`🔧 Using default WebTorrent trackers`);
}

// Fix EventEmitter warning when seeding multiple torrents
// WebTorrent reuses a single DHT instance, causing listener count to exceed default limit
Expand Down Expand Up @@ -588,7 +602,18 @@
reject(new Error("WebTorrent seeding timeout"));
}, 30000); // 30 second timeout for seeding

this.webTorrentClient!.seed(filePath, (torrent) => {
// Seed with custom trackers if configured
const seedOptions = this.options.trackers && this.options.trackers.length > 0

Check warning on line 606 in src/host.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
? { announce: this.options.trackers }

Check warning on line 607 in src/host.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: undefined;

if (seedOptions) {
this.logger.debug(`🔧 Using custom trackers for seeding: ${this.options.trackers!.join(', ')}`);

Check warning on line 611 in src/host.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {
this.logger.debug(`🔧 Using default trackers for seeding`);
}

Check warning on line 614 in src/host.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

this.webTorrentClient!.seed(filePath, seedOptions, (torrent) => {
clearTimeout(seedTimeout);
const magnetURI = torrent.magnetURI;
this.magnetUris.set(filename, magnetURI);
Expand Down
Loading
Loading