mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
Fix corner cases in connect/disconnect lifecycle and resource cleanup
1. Move TS6 handler patch to after client.connect() — the library's connect() internally replaces handler via #S(), discarding any patch applied beforehand. Patching after connect() is safe because clientinit is sent in async message callbacks after Init1 round-trips. 2. Preserve detectedProtocol across reconnect — disconnect() resets it to "unknown", causing the TS6 patch to be skipped on reconnect. 3. Prevent double "disconnected" event in BotInstance — disconnect() emitted it directly AND the async TS3Client disconnect triggered another through the event chain. 4. Guard playNext() against running after disconnect — check connected flag to avoid ghost queue processing. 5. Fix UDP error timer leak — clear previous timer before setting a new one to prevent accumulation. 6. Fix isPortFree() FD leak — close the test server on error path. https://claude.ai/code/session_01QzvMLUT3UkhsffShcY1qzD
This commit is contained in:
3 files changed
+51
-28
No files matched your search
+40
-24
@@ -119,10 +119,15 @@ export class TS3Client extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
// Guard against calling connect() while already connected
|
||||
// Guard against calling connect() while already connected.
|
||||
// Save detectedProtocol first because disconnect() resets it.
|
||||
if (this.client) {
|
||||
this.logger.warn("connect() called while already connected, disconnecting first");
|
||||
const savedProtocol = this.detectedProtocol;
|
||||
const savedHttpQuery = this.httpQuery;
|
||||
this.disconnect();
|
||||
this.detectedProtocol = savedProtocol;
|
||||
this.httpQuery = savedHttpQuery;
|
||||
// Give the old client a moment to tear down
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
@@ -139,7 +144,9 @@ export class TS3Client extends EventEmitter {
|
||||
udpErrorCount++;
|
||||
if (udpErrorCount === 1) {
|
||||
this.logger.warn(msg);
|
||||
// After 2 seconds, log a summary and reset
|
||||
// After 2 seconds, log a summary and reset.
|
||||
// Clear any previous timer to avoid leaking it.
|
||||
if (this.udpErrorTimer) clearTimeout(this.udpErrorTimer);
|
||||
this.udpErrorTimer = setTimeout(() => {
|
||||
if (udpErrorCount > 1) {
|
||||
this.logger.warn(`udp send error (repeated ${udpErrorCount} times, connection may be lost)`);
|
||||
@@ -162,28 +169,6 @@ export class TS3Client extends EventEmitter {
|
||||
},
|
||||
});
|
||||
|
||||
// Patch handler.sendPacket to intercept clientinit during handshake for TS6.
|
||||
// The library's commandMiddleware only applies to post-connection commands
|
||||
// (sendCommandNoWait/execCommand), but the handshake's clientinit is sent
|
||||
// directly via handler.sendPacket, bypassing middleware entirely.
|
||||
// This monkey-patch ensures the version is upgraded for TS6 servers.
|
||||
if (this.detectedProtocol === "ts6") {
|
||||
this.logger.info("Applying TS6 compatibility: upgrading client_version to 3.6.2");
|
||||
const origSendPacket = this.client.handler.sendPacket.bind(this.client.handler);
|
||||
this.client.handler.sendPacket = (pType: number, data: Uint8Array, flags: number) => {
|
||||
// PacketType.Command = 2
|
||||
if (pType === 2) {
|
||||
const str = Buffer.from(data).toString("utf-8");
|
||||
if (str.startsWith("clientinit ")) {
|
||||
const patched = patchClientInitVersion(str);
|
||||
origSendPacket(pType, Buffer.from(patched), flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
origSendPacket(pType, data, flags);
|
||||
};
|
||||
}
|
||||
|
||||
this.client.on("textMessage", (msg: TextMessage) => {
|
||||
const tsMsg: TS3TextMessage = {
|
||||
invokerName: msg.invokerName,
|
||||
@@ -209,6 +194,37 @@ export class TS3Client extends EventEmitter {
|
||||
});
|
||||
|
||||
await this.client.connect();
|
||||
|
||||
// Patch handler.sendPacket to intercept clientinit during handshake for TS6.
|
||||
//
|
||||
// Why after connect(): The library's connect() internally calls #S() which
|
||||
// replaces this.handler with a fresh PacketHandler. Any patch applied before
|
||||
// connect() would be discarded. Patching here is safe because connect() only
|
||||
// establishes the UDP socket and sends the first Init1 packet — the actual
|
||||
// clientinit command is sent later in async message callbacks (after Init1
|
||||
// round-trips complete), which cannot fire before this sync continuation.
|
||||
//
|
||||
// Why not commandMiddleware: The library's middleware chain only applies to
|
||||
// post-connection commands (sendCommandNoWait/execCommand). The handshake's
|
||||
// clientinit is sent directly via handler.sendPacket(), bypassing middleware.
|
||||
if (this.detectedProtocol === "ts6") {
|
||||
this.logger.info("Applying TS6 compatibility: upgrading client_version to 3.6.2");
|
||||
const handler = this.client.handler;
|
||||
const origSendPacket = handler.sendPacket.bind(handler);
|
||||
handler.sendPacket = (pType: number, data: Uint8Array, flags: number) => {
|
||||
// PacketType.Command = 2
|
||||
if (pType === 2) {
|
||||
const str = Buffer.from(data).toString("utf-8");
|
||||
if (str.startsWith("clientinit ")) {
|
||||
const patched = patchClientInitVersion(str);
|
||||
origSendPacket(pType, Buffer.from(patched), flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
origSendPacket(pType, data, flags);
|
||||
};
|
||||
}
|
||||
|
||||
await this.client.waitConnected();
|
||||
this.clientId = this.client.clientID();
|
||||
this.voiceFramesSent = 0;
|
||||
|
||||
Reference in new issue
Block a user