Files
teamspeak-music-bot/src/web/websocket.ts
T
Claude 1c88dd7a35 Add comprehensive error handling to prevent process crashes
- Global uncaughtException/unhandledRejection handlers in index.ts
- FFmpeg stdout/stderr stream error handlers in player.ts
- HTTP server and WebSocket server error handlers in server.ts
- Safe WebSocket broadcast with try-catch in websocket.ts
- Catch async errors from textMessage handler in instance.ts
- Reset voiceFramesSent counter on reconnect in client.ts

https://claude.ai/code/session_01EjpEsC2GCsvwbu4n3XC8EE
2026-03-31 16:58:14 +00:00

115 lines
2.9 KiB
TypeScript

import { WebSocketServer, WebSocket } from "ws";
import type { BotManager } from "../bot/manager.js";
import type { BotInstance } from "../bot/instance.js";
import type { Logger } from "../logger.js";
export function setupWebSocket(
wss: WebSocketServer,
botManager: BotManager,
logger: Logger
): () => void {
const clients = new Set<WebSocket>();
/** Track which bots already have listeners attached */
const attachedBots = new Map<string, {
stateChange: () => void;
connected: () => void;
disconnected: () => void;
}>();
wss.on("connection", (ws) => {
clients.add(ws);
logger.debug("WebSocket client connected");
const bots = botManager.getAllBots().map((b) => b.getStatus());
ws.send(JSON.stringify({ type: "init", bots }));
ws.on("close", () => {
clients.delete(ws);
logger.debug("WebSocket client disconnected");
});
ws.on("error", (err) => {
logger.error({ err }, "WebSocket error");
clients.delete(ws);
});
});
const broadcast = (data: object) => {
const message = JSON.stringify(data);
for (const client of clients) {
if (client.readyState === WebSocket.OPEN) {
try {
client.send(message);
} catch {
clients.delete(client);
}
}
}
};
function attachBotListener(bot: BotInstance): void {
if (attachedBots.has(bot.id)) return;
const onStateChange = () => {
broadcast({
type: "stateChange",
botId: bot.id,
status: bot.getStatus(),
queue: bot.getQueue(),
});
};
const onConnected = () => {
broadcast({
type: "botConnected",
botId: bot.id,
status: bot.getStatus(),
});
};
const onDisconnected = () => {
broadcast({
type: "botDisconnected",
botId: bot.id,
status: bot.getStatus(),
});
};
bot.on("stateChange", onStateChange);
bot.on("connected", onConnected);
bot.on("disconnected", onDisconnected);
attachedBots.set(bot.id, {
stateChange: onStateChange,
connected: onConnected,
disconnected: onDisconnected,
});
}
/** Attach listeners for any new bots that don't have them yet */
function ensureAllBotsAttached(): void {
for (const bot of botManager.getAllBots()) {
attachBotListener(bot);
}
}
// Check for newly added bots periodically
const intervalId = setInterval(ensureAllBotsAttached, 5000);
ensureAllBotsAttached();
return () => {
clearInterval(intervalId);
// Clean up named listeners
for (const bot of botManager.getAllBots()) {
const listeners = attachedBots.get(bot.id);
if (listeners) {
bot.removeListener("stateChange", listeners.stateChange);
bot.removeListener("connected", listeners.connected);
bot.removeListener("disconnected", listeners.disconnected);
}
}
attachedBots.clear();
};
}