diff --git a/src/audio/player.ts b/src/audio/player.ts index e5c52c0..03be44f 100644 --- a/src/audio/player.ts +++ b/src/audio/player.ts @@ -128,6 +128,14 @@ export class AudioPlayer extends EventEmitter { this.logger.info({ ffmpeg: ffmpegBin }, "Using ffmpeg binary"); this.ffmpeg = spawn(ffmpegBin, args, { stdio: ["ignore", "pipe", "pipe"] }); + // Prevent stream errors from crashing the process + this.ffmpeg.stdout!.on("error", (err) => { + this.logger.warn({ err }, "FFmpeg stdout error"); + }); + this.ffmpeg.stderr!.on("error", (err) => { + this.logger.warn({ err }, "FFmpeg stderr error"); + }); + let gotFirstData = false; this.ffmpeg.stdout!.on("data", (chunk: Buffer) => { if (!gotFirstData) { diff --git a/src/bot/instance.ts b/src/bot/instance.ts index 69921ad..0058824 100644 --- a/src/bot/instance.ts +++ b/src/bot/instance.ts @@ -99,7 +99,9 @@ export class BotInstance extends EventEmitter { private setupTsEvents(): void { this.tsClient.on("textMessage", (msg: TS3TextMessage) => { - this.handleTextMessage(msg); + this.handleTextMessage(msg).catch((err) => { + this.logger.error({ err }, "Unhandled error in text message handler"); + }); }); this.tsClient.on("disconnected", () => { diff --git a/src/index.ts b/src/index.ts index 554825a..14beb61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,14 @@ async function main() { saveConfig(CONFIG_PATH, config); const logger = createLogger(LOG_DIR); + + // Prevent unhandled errors from crashing the process + process.on("uncaughtException", (err) => { + logger.error({ err }, "Uncaught exception"); + }); + process.on("unhandledRejection", (reason) => { + logger.error({ reason }, "Unhandled promise rejection"); + }); const db = createDatabase(DB_PATH); const apiServer = createApiServerManager( diff --git a/src/ts-protocol/client.ts b/src/ts-protocol/client.ts index 1426801..63e5c04 100644 --- a/src/ts-protocol/client.ts +++ b/src/ts-protocol/client.ts @@ -114,6 +114,7 @@ export class TS3Client extends EventEmitter { await this.client.connect(); await this.client.waitConnected(); this.clientId = this.client.clientID(); + this.voiceFramesSent = 0; this.logger.info({ clientId: this.clientId }, "Logged in (visible client)"); // Join default channel if specified diff --git a/src/web/server.ts b/src/web/server.ts index af9780d..7a5621d 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -67,7 +67,14 @@ export function createWebServer(options: WebServerOptions): WebServer { }); } + server.on("error", (err) => { + logger.error({ err }, "HTTP server error"); + }); + const wss = new WebSocketServer({ server, path: "/ws" }); + wss.on("error", (err) => { + logger.error({ err }, "WebSocket server error"); + }); const cleanupWs = setupWebSocket(wss, options.botManager, logger); return { diff --git a/src/web/websocket.ts b/src/web/websocket.ts index 916f820..fc8813d 100644 --- a/src/web/websocket.ts +++ b/src/web/websocket.ts @@ -39,7 +39,11 @@ export function setupWebSocket( const message = JSON.stringify(data); for (const client of clients) { if (client.readyState === WebSocket.OPEN) { - client.send(message); + try { + client.send(message); + } catch { + clients.delete(client); + } } } };