Skip to content

Commit 4001472

Browse files
committed
Added a safer way to dispose old subscriptions
Signed-off-by: Robert Gogete <gogeterobert@yahoo.com>
1 parent dec377e commit 4001472

File tree

1 file changed

+86
-8
lines changed

1 file changed

+86
-8
lines changed

src/client.ts

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ export interface MetadataEvent {
4141
infoHash: string; // The torrent info hash
4242
}
4343

44+
// Global flag to ensure we only add the uncaught exception handler once
45+
let globalErrorHandlerAdded = false;
46+
4447
export class FileClient extends EventEmitter implements IFileClient {
4548
private gunRegistry: GunRegistry;
4649
private webTorrentClient: WebTorrent.Instance | null = null;
@@ -50,6 +53,9 @@ export class FileClient extends EventEmitter implements IFileClient {
5053
constructor(options: FileClientOptions = {}) {
5154
super(); // Call EventEmitter constructor
5255

56+
// Increase max listeners to prevent warnings during testing/multiple usage
57+
this.setMaxListeners(20);
58+
5359
this.options = {
5460
peers: options.peers || ["http://dig-relay-prod.eba-2cmanxbe.us-east-1.elasticbeanstalk.com/gun"],
5561
namespace: options.namespace || "dig-nat-tools",
@@ -235,6 +241,33 @@ export class FileClient extends EventEmitter implements IFileClient {
235241
};
236242
}
237243

244+
/**
245+
* Safely destroy a torrent with error handling for WebTorrent internal issues
246+
*/
247+
private safeTorrentDestroy(torrent: WebTorrent.Torrent): void {
248+
try {
249+
torrent.destroy();
250+
} catch (error) {
251+
const errorCode = (error as unknown as { code?: string }).code;
252+
if (errorCode === 'ERR_INVALID_ARG_TYPE' && error instanceof Error && error.message.includes('listener')) {
253+
this.logger.warn("⚠️ WebTorrent internal error during torrent destroy (handled):", {
254+
message: error.message,
255+
code: errorCode,
256+
torrentName: torrent.name || 'Unknown',
257+
infoHash: torrent.infoHash || 'Unknown'
258+
});
259+
} else {
260+
this.logger.error("❌ Error destroying torrent:", {
261+
...this.serializeError(error),
262+
torrentName: torrent.name || 'Unknown',
263+
infoHash: torrent.infoHash || 'Unknown'
264+
});
265+
// Re-throw non-listener errors as they might be important
266+
throw error;
267+
}
268+
}
269+
}
270+
238271
/**
239272
* Parse magnet URI for debugging purposes
240273
*/
@@ -288,6 +321,9 @@ export class FileClient extends EventEmitter implements IFileClient {
288321
try {
289322
this.webTorrentClient = new WebTorrent();
290323

324+
// Increase max listeners for the WebTorrent client to prevent warnings
325+
this.webTorrentClient.setMaxListeners(20);
326+
291327
// Log client status (using safe property access)
292328
this.logger.debug(`🔧 WebTorrent client created:`, {
293329
activeTorrents: this.webTorrentClient.torrents.length,
@@ -308,6 +344,26 @@ export class FileClient extends EventEmitter implements IFileClient {
308344
});
309345
});
310346

347+
// Add global error handling for uncaught WebTorrent internal errors (only once)
348+
if (!globalErrorHandlerAdded) {
349+
globalErrorHandlerAdded = true;
350+
process.on('uncaughtException', (error) => {
351+
const errorCode = (error as unknown as { code?: string }).code;
352+
if (error.message && error.message.includes('listener') && errorCode === 'ERR_INVALID_ARG_TYPE') {
353+
// Use console.warn directly since we can't access logger from global scope
354+
console.warn("⚠️ WebTorrent internal event listener error (handled):", {
355+
message: error.message,
356+
code: errorCode,
357+
stack: error.stack?.split('\n').slice(0, 5).join('\n') // Truncate stack trace
358+
});
359+
// Don't re-throw this specific error as it's a WebTorrent internal cleanup issue
360+
} else {
361+
// Re-throw other uncaught exceptions
362+
throw error;
363+
}
364+
});
365+
}
366+
311367
this.logger.debug(`✅ WebTorrent client initialized`);
312368
}
313369

@@ -347,7 +403,7 @@ export class FileClient extends EventEmitter implements IFileClient {
347403
);
348404

349405
if (torrent!.files.length === 0) {
350-
torrent!.destroy();
406+
this.safeTorrentDestroy(torrent!);
351407
this.logger.error("❌ No files in torrent", {
352408
name: torrent!.name,
353409
infoHash: torrent!.infoHash,
@@ -359,7 +415,7 @@ export class FileClient extends EventEmitter implements IFileClient {
359415

360416
// Check file size against maximum allowed size
361417
if (options.maxFileSizeBytes && torrent!.length > options.maxFileSizeBytes) {
362-
torrent!.destroy();
418+
this.safeTorrentDestroy(torrent!);
363419
const fileSizeMB = (torrent!.length / (1024 * 1024)).toFixed(2);
364420
const maxSizeMB = (options.maxFileSizeBytes / (1024 * 1024)).toFixed(2);
365421
this.logger.warn(`⚠️ File too large: ${fileSizeMB}MB > ${maxSizeMB}MB`);
@@ -388,12 +444,12 @@ export class FileClient extends EventEmitter implements IFileClient {
388444
);
389445

390446
// Destroy torrent to clean up
391-
torrent!.destroy();
447+
this.safeTorrentDestroy(torrent!);
392448
resolve(buffer);
393449
});
394450

395451
stream.on("error", (error: unknown) => {
396-
torrent!.destroy();
452+
this.safeTorrentDestroy(torrent!);
397453
this.logger.error("❌ Stream error during download:", {
398454
...this.serializeError(error),
399455
fileName: file.name,
@@ -431,7 +487,7 @@ export class FileClient extends EventEmitter implements IFileClient {
431487
// Add download progress event emission
432488
torrent.on("download", (_bytes: number) => {
433489
const progressData: DownloadProgressEvent = {
434-
downloaded: torrent!.downloaded,
490+
downloaded: torrent!.downloaded * 100,
435491
downloadSpeed: torrent!.downloadSpeed * 100,
436492
progress: torrent!.progress * 100,
437493
name: torrent!.name || 'Unknown',
@@ -656,9 +712,31 @@ export class FileClient extends EventEmitter implements IFileClient {
656712
*/
657713
public async destroy(): Promise<void> {
658714
if (this.webTorrentClient) {
659-
this.webTorrentClient.destroy();
660-
this.webTorrentClient = null;
661-
this.logger.debug("✅ WebTorrent client destroyed");
715+
try {
716+
// First destroy all active torrents safely
717+
if (this.webTorrentClient.torrents) {
718+
for (const torrent of this.webTorrentClient.torrents) {
719+
this.safeTorrentDestroy(torrent);
720+
}
721+
}
722+
723+
// Then destroy the client
724+
this.webTorrentClient.destroy();
725+
this.webTorrentClient = null;
726+
this.logger.debug("✅ WebTorrent client destroyed");
727+
} catch (error) {
728+
const errorCode = (error as unknown as { code?: string }).code;
729+
if (errorCode === 'ERR_INVALID_ARG_TYPE' && error instanceof Error && error.message.includes('listener')) {
730+
this.logger.warn("⚠️ WebTorrent cleanup error (handled):", {
731+
message: error.message,
732+
code: errorCode
733+
});
734+
} else {
735+
this.logger.error("❌ Error destroying WebTorrent client:", this.serializeError(error));
736+
throw error;
737+
}
738+
this.webTorrentClient = null;
739+
}
662740
}
663741
}
664742
}

0 commit comments

Comments
 (0)