fix: address Phase 2 review — error handlers, drain queue, prevent listener accumulation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.6 committed 2026-03-30 00:32:22 +08:00
1 parent a6bfd9d32c
commit d66355a829
3 files changed
+39 -25

No files matched your search

+29 -19
View File
@@ -49,6 +49,35 @@ export class TS3Client extends EventEmitter {
port: options.port,
});
this.connection.on("error", (err) => {
this.logger.error({ err }, "TCP connection error");
this.emit("error", err);
});
this.voice.on("error", (err) => {
this.logger.error({ err }, "UDP voice error");
this.emit("error", err);
});
this.connection.on(
"notify:textmessage",
(data: Record<string, string>) => {
const msg: TS3TextMessage = {
invokerName: data.invokername ?? "",
invokerId: data.invokerid ?? "",
invokerUid: data.invokeruid ?? "",
message: data.msg ?? "",
targetMode: parseInt(data.targetmode ?? "0", 10),
};
this.emit("textMessage", msg);
}
);
this.connection.on("close", () => {
this.logger.warn("Connection closed");
this.emit("disconnected");
});
if (options.identity) {
this.identity = importIdentity(options.identity);
} else {
@@ -91,27 +120,8 @@ export class TS3Client extends EventEmitter {
await this.sendCommand("servernotifyregister", { event: "textchannel" });
await this.sendCommand("servernotifyregister", { event: "textprivate" });
this.connection.on(
"notify:textmessage",
(data: Record<string, string>) => {
const msg: TS3TextMessage = {
invokerName: data.invokername ?? "",
invokerId: data.invokerid ?? "",
invokerUid: data.invokeruid ?? "",
message: data.msg ?? "",
targetMode: parseInt(data.targetmode ?? "0", 10),
};
this.emit("textMessage", msg);
}
);
this.keepAliveInterval = setInterval(() => {
this.voice.sendKeepAlive();
}, 5000);
this.connection.on("close", () => {
this.logger.warn("Connection closed");
this.emit("disconnected");
});
this.emit("connected");
-6
View File
@@ -62,12 +62,6 @@ export function decodeResponse(raw: string): Record<string, string>[] {
});
}
export interface TS3Response {
errorId: number;
errorMessage: string;
data: Record<string, string>[];
}
export function parseErrorLine(line: string): { id: number; msg: string } {
const decoded = decodeResponse(line.replace(/^error\s+/, ""))[0];
return {
+10
View File
@@ -48,6 +48,7 @@ export class TS3Connection extends EventEmitter {
});
this.socket.on("close", () => {
this.connected = false;
this.drainCommandQueue(new Error("Connection closed"));
this.emit("close");
});
});
@@ -126,6 +127,15 @@ export class TS3Connection extends EventEmitter {
this.socket = null;
this.connected = false;
}
this.drainCommandQueue(new Error("Disconnected"));
}
private drainCommandQueue(error: Error): void {
for (const pending of this.commandQueue) {
pending.reject(error);
}
this.commandQueue = [];
this.responseLines = [];
}
isConnected(): boolean {