Skip to content

Commit b09b88b

Browse files
committed
added choking and unchoking logic
Signed-off-by: Robert Gogete <gogeterobert@yahoo.com>
1 parent 1bd924a commit b09b88b

File tree

1 file changed

+201
-97
lines changed

1 file changed

+201
-97
lines changed

src/webtorrent-manager.ts

Lines changed: 201 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ class WebTorrentManager extends EventEmitter {
9999
this.logger.debug("🚀 Initializing shared WebTorrent client...");
100100

101101
try {
102-
this.webTorrentClient = new WebTorrent();
102+
this.webTorrentClient = new WebTorrent({
103+
maxConns: 100, // Increase max connections (default: 55)
104+
downloadLimit: -1, // No download limit
105+
uploadLimit: -1 // No upload limit (helps with reciprocity)
106+
});
103107

104108
// Set unlimited max listeners for the WebTorrent client (if method exists)
105109
if (typeof this.webTorrentClient.setMaxListeners === 'function') {
@@ -209,110 +213,185 @@ class WebTorrentManager extends EventEmitter {
209213

210214
this.logger.debug(`🔄 Adding torrent from magnet URI: ${magnetUri.substring(0, 100)}...`);
211215

212-
// Use the callback form of add() for event-driven approach
213-
var torrent = this.webTorrentClient.add(magnetUri);
214-
215-
this.logger.info(`📦 Torrent added: ${torrent.name || 'Unknown'}, size: ${torrent.length} bytes`);
216-
// Check file size after metadata is received
217-
// torrent.on("ready", () => {
218-
// this.logger.info(
219-
// `✅ Torrent ready! File: ${torrent.name}, Size: ${torrent.length} bytes, Files: ${torrent.files.length}`
220-
// );
221-
222-
// if (torrent.files.length === 0) {
223-
// const error = new Error("No files in torrent");
224-
// this.logger.error("❌ No files in torrent", {
225-
// name: torrent.name,
226-
// infoHash: torrent.infoHash,
227-
// magnetUri: magnetUri.substring(0, 100) + '...'
228-
// });
229-
// this.emit('download-error', { magnetUri, error });
230-
// this.safeTorrentDestroy(torrent);
231-
// return;
232-
// }
233-
234-
// // Check file size against maximum allowed size
235-
// if (maxFileSizeBytes && torrent.length > maxFileSizeBytes) {
236-
// const fileSizeMB = (torrent.length / (1024 * 1024)).toFixed(2);
237-
// const maxSizeMB = (maxFileSizeBytes / (1024 * 1024)).toFixed(2);
238-
// const error = new Error(
239-
// `File size (${fileSizeMB} MB) exceeds maximum allowed size (${maxSizeMB} MB). Download cancelled.`
240-
// );
241-
// this.logger.warn(`⚠️ File too large: ${fileSizeMB}MB > ${maxSizeMB}MB`);
242-
// this.emit('download-error', { magnetUri, error });
243-
// this.safeTorrentDestroy(torrent);
244-
// return;
245-
// }
246-
247-
// // Use the 'done' event which fires when all pieces are downloaded
248-
// // This handles both fresh downloads and already-complete torrents
249-
// torrent.on("done", () => {
250-
// this.logger.info(`🎉 Torrent download complete: ${torrent.name}`);
251-
252-
// const file = torrent.files[0]; // Get the first file
253-
// const chunks: Buffer[] = [];
254-
255-
// this.logger.debug(`📥 Reading file data: ${file.name} (${file.length} bytes)...`);
256-
257-
// // Create a stream to read the file
258-
// const stream = file.createReadStream();
259-
260-
// stream.on("data", (chunk: Buffer) => {
261-
// chunks.push(chunk);
262-
// });
263-
264-
// stream.on("end", () => {
265-
// const buffer = Buffer.concat(chunks);
266-
// this.logger.debug(
267-
// `✅ File read completed! ${buffer.length} bytes`
268-
// );
269-
270-
// // Emit download complete event with buffer
271-
// this.emit('download-complete', {
272-
// magnetUri,
273-
// buffer,
274-
// name: torrent.name || 'Unknown',
275-
// size: buffer.length
276-
// });
277-
278-
// // Destroy torrent to clean up
279-
// this.safeTorrentDestroy(torrent);
280-
// });
281-
282-
// stream.on("error", (error: unknown) => {
283-
// this.logger.error("❌ Stream error during file read:", {
284-
// ...this.serializeError(error),
285-
// fileName: file.name,
286-
// fileLength: file.length
287-
// });
288-
// this.emit('download-error', { magnetUri, error });
289-
// this.safeTorrentDestroy(torrent);
290-
// });
291-
// });
292-
// });
293-
torrent.on('metadata', () => {
294-
this.logger.info(
295-
`✅ Metadata received! Torrent: ${torrent.name}, Size: ${torrent.length} bytes, Files: ${torrent.files.length}`
216+
// Add the torrent
217+
const torrent = this.webTorrentClient.add(magnetUri);
218+
219+
this.logger.info(`📦 Torrent added: ${torrent.name || 'Unknown'}`);
220+
221+
// ANTI-CHOKING STRATEGY: Handle wire connections to express interest immediately
222+
torrent.on('wire', (wire: any, addr: string) => {
223+
this.logger.debug(`🔌 Connected to peer: ${addr}`);
224+
225+
// Express interest immediately when connected
226+
wire.interested();
227+
this.logger.debug(`✋ Expressed interest to peer: ${addr}`);
228+
229+
// Handle choke events - re-express interest when choked
230+
wire.on('choke', () => {
231+
this.logger.debug(`🚫 Choked by peer: ${addr} - re-expressing interest`);
232+
if (!wire.amInterested) {
233+
wire.interested(); // Request to be unchoked
234+
}
235+
});
236+
237+
// Log unchoke events
238+
wire.on('unchoke', () => {
239+
this.logger.debug(`✅ Unchoked by peer: ${addr} - can download`);
240+
});
241+
242+
// Log when peer is interested
243+
wire.on('interested', () => {
244+
this.logger.debug(`👀 Peer ${addr} is interested in our data`);
245+
});
246+
247+
// Log when peer is not interested
248+
wire.on('uninterested', () => {
249+
this.logger.debug(`😴 Peer ${addr} is not interested in our data`);
250+
});
251+
});
252+
253+
// Emit metadata event
254+
torrent.on('metadata', () => {
255+
this.logger.info(
256+
`� Metadata received: ${torrent.name}, Size: ${(torrent.length / 1024 / 1024).toFixed(2)} MB, Files: ${torrent.files.length}`
257+
);
258+
259+
const metadataData: MetadataEvent = {
260+
name: torrent.name || 'Unknown',
261+
size: torrent.length,
262+
magnetUri: magnetUri,
263+
infoHash: torrent.infoHash || 'Unknown'
264+
};
265+
266+
this.emit('metadata', metadataData);
267+
268+
setTimeout(() => {
269+
this.logger.debug(`👥 Peers sharing this torrent: ${torrent.numPeers}`);
270+
}, 1000);
271+
});
272+
273+
// Check file size after metadata is received
274+
torrent.once('ready', () => {
275+
this.logger.info(
276+
`✅ Torrent ready! File: ${torrent.name}, Size: ${torrent.length} bytes, Files: ${torrent.files.length}`
277+
);
278+
279+
if (torrent.files.length === 0) {
280+
const error = new Error("No files in torrent");
281+
this.logger.error("❌ No files in torrent", {
282+
name: torrent.name,
283+
infoHash: torrent.infoHash,
284+
magnetUri: magnetUri.substring(0, 100) + '...'
285+
});
286+
this.emit('download-error', { magnetUri, error });
287+
this.safeTorrentDestroy(torrent);
288+
return;
289+
}
290+
291+
// Check file size against maximum allowed size
292+
if (maxFileSizeBytes && torrent.length > maxFileSizeBytes) {
293+
const fileSizeMB = (torrent.length / (1024 * 1024)).toFixed(2);
294+
const maxSizeMB = (maxFileSizeBytes / (1024 * 1024)).toFixed(2);
295+
const error = new Error(
296+
`File size (${fileSizeMB} MB) exceeds maximum allowed size (${maxSizeMB} MB). Download cancelled.`
296297
);
298+
this.logger.warn(`⚠️ File too large: ${fileSizeMB}MB > ${maxSizeMB}MB`);
299+
this.emit('download-error', { magnetUri, error });
300+
this.safeTorrentDestroy(torrent);
301+
return;
302+
}
303+
304+
// Use the 'done' event which fires when all pieces are downloaded
305+
torrent.once('done', () => {
306+
this.logger.info(`🎉 Torrent download complete: ${torrent.name}`);
307+
308+
const file = torrent.files[0]; // Get the first file
309+
const chunks: Buffer[] = [];
310+
311+
this.logger.debug(`📥 Reading file data: ${file.name} (${file.length} bytes)...`);
297312

298-
setTimeout(() => {
299-
this.logger.info(`peers sharing this torrent: ${torrent.numPeers}`);
300-
}, 1000);
313+
// Create a stream to read the file
314+
const stream = file.createReadStream();
315+
316+
stream.on('data', (chunk: Buffer) => {
317+
chunks.push(chunk);
318+
});
319+
320+
stream.on('end', () => {
321+
const buffer = Buffer.concat(chunks);
322+
this.logger.info(
323+
`✅ File read completed! ${buffer.length} bytes`
324+
);
325+
326+
// Emit download complete event with buffer
327+
this.emit('download-complete', {
328+
magnetUri,
329+
buffer,
330+
name: torrent.name || 'Unknown',
331+
size: buffer.length
332+
});
333+
334+
// Destroy torrent to clean up
335+
this.safeTorrentDestroy(torrent);
336+
});
337+
338+
stream.on('error', (error: unknown) => {
339+
this.logger.error("❌ Stream error during file read:", {
340+
...this.serializeError(error),
341+
fileName: file.name,
342+
fileLength: file.length
343+
});
344+
this.emit('download-error', { magnetUri, error });
345+
this.safeTorrentDestroy(torrent);
346+
});
301347
});
302-
torrent.on('upload', (bytes) => {
303-
this.logger.debug(`⬆️ Uploaded ${bytes} bytes`);
348+
});
349+
350+
// Enhanced torrent error handling
351+
torrent.on('error', (error: unknown) => {
352+
this.logger.error(`❌ WebTorrent torrent error:`, {
353+
...this.serializeError(error),
354+
magnetUri: magnetUri.substring(0, 100) + '...',
355+
infoHash: torrent.infoHash,
356+
torrentName: torrent.name
304357
});
358+
this.emit('download-error', { magnetUri, error });
359+
});
305360

306-
torrent.on('wire', (wire, addr) => {
307-
this.logger.debug(`⬆️ New peer connected: ${addr}`);
361+
// Add torrent event listeners for debugging
362+
torrent.on('warning', (warning: unknown) => {
363+
this.logger.debug("⚠️ WebTorrent warning:", {
364+
...this.serializeError(warning)
308365
});
366+
});
309367

310-
torrent.on('download', (bytes) => {
311-
this.logger.debug(`⬇️ Downloaded ${bytes} bytes`);
312-
this.logger.debug(`📦 Total downloaded: ${torrent.downloaded} bytes`);
313-
this.logger.debug(`⚡ Download speed: ${torrent.downloadSpeed} bytes/sec`);
314-
this.logger.debug(`📈 Progress: ${torrent.progress} %`);
368+
torrent.on('noPeers', () => {
369+
this.logger.warn("⚠️ No peers found for torrent", {
370+
magnetUri: magnetUri.substring(0, 100) + '...',
371+
infoHash: torrent.infoHash,
372+
name: torrent.name
315373
});
374+
});
375+
376+
// Add download progress event emission
377+
torrent.on('download', (_bytes: number) => {
378+
const progressData: DownloadProgressEvent = {
379+
downloaded: torrent.downloaded,
380+
downloadSpeed: torrent.downloadSpeed,
381+
progress: torrent.progress,
382+
name: torrent.name || 'Unknown',
383+
magnetUri: magnetUri
384+
};
385+
386+
this.logger.debug(`📊 Download progress: ${(progressData.progress * 100).toFixed(1)}% - ${progressData.name}`, {
387+
downloaded: `${(progressData.downloaded / (1024 * 1024)).toFixed(2)}MB`,
388+
speed: `${(progressData.downloadSpeed / (1024 * 1024)).toFixed(2)}MB/s`,
389+
progress: `${(progressData.progress * 100).toFixed(1)}%`
390+
});
391+
392+
// Emit the download event that external code can listen to
393+
this.emit('download', progressData);
394+
});
316395
}
317396

318397
/**
@@ -331,6 +410,31 @@ class WebTorrentManager extends EventEmitter {
331410
const magnetURI = torrent.magnetURI;
332411
this.logger.debug(`🧲 File seeded successfully: ${filePath}`);
333412
this.logger.debug(` Magnet URI: ${magnetURI}`);
413+
414+
// ANTI-CHOKING STRATEGY FOR SEEDING: Immediately unchoke all connected peers
415+
torrent.on('wire', (wire: any, addr: string) => {
416+
this.logger.debug(`🔌 Peer connected to seeder: ${addr}`);
417+
418+
// Immediately unchoke the peer
419+
wire.unchoke();
420+
this.logger.debug(`✅ Unchoked peer: ${addr}`);
421+
422+
// Keep peers unchoked when they show interest
423+
wire.on('interested', () => {
424+
this.logger.debug(`👀 Peer ${addr} interested - ensuring unchoked`);
425+
wire.unchoke();
426+
});
427+
428+
// Log choke/unchoke state changes
429+
wire.on('choke', () => {
430+
this.logger.debug(`🚫 We choked peer: ${addr}`);
431+
});
432+
433+
wire.on('unchoke', () => {
434+
this.logger.debug(`✅ We unchoked peer: ${addr}`);
435+
});
436+
});
437+
334438
resolve(magnetURI);
335439
});
336440
} catch (error) {

0 commit comments

Comments
 (0)