mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 03:43:11 +08:00
@@ -49,12 +49,20 @@ describe('WsBridge', () => {
|
||||
expect(bridge.sendStopGeneration('chat-stop')).toBe(false)
|
||||
})
|
||||
|
||||
it('destroy cleans up all sessions', () => {
|
||||
it('destroy cleans up all sessions without leaking connecting-socket errors', async () => {
|
||||
bridge.connectSession('a', 'uuid-a')
|
||||
bridge.connectSession('b', 'uuid-b')
|
||||
const sockets = [...(bridge as any).sessions.values()]
|
||||
.map((session: any) => session.ws)
|
||||
bridge.destroy()
|
||||
expect(bridge.hasSession('a')).toBe(false)
|
||||
expect(bridge.hasSession('b')).toBe(false)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||
for (const ws of sockets) {
|
||||
expect(ws.listenerCount('error')).toBe(0)
|
||||
expect(ws.listenerCount('close')).toBe(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -225,6 +233,13 @@ describe('WsBridge: handler serialization', () => {
|
||||
bridge.resetSession('chat-reset')
|
||||
expect(bridge.hasSession('chat-reset')).toBe(false)
|
||||
expect(staleSession.ws.listenerCount('message')).toBe(0)
|
||||
await new Promise<void>((resolve) => {
|
||||
if (staleSession.ws.readyState === staleSession.ws.CLOSED) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
staleSession.ws.once('close', () => resolve())
|
||||
})
|
||||
expect(staleSession.ws.listenerCount('close')).toBe(0)
|
||||
expect(staleSession.ws.listenerCount('error')).toBe(0)
|
||||
|
||||
|
||||
@@ -135,8 +135,7 @@ export class WsBridge {
|
||||
const session = this.sessions.get(chatId)
|
||||
if (session) {
|
||||
if (session.reconnectTimer) clearTimeout(session.reconnectTimer)
|
||||
session.ws.removeAllListeners()
|
||||
session.ws.close(1000, 'session reset')
|
||||
this.closeSocket(session.ws, 1000, 'session reset')
|
||||
this.sessions.delete(chatId)
|
||||
}
|
||||
this.handlers.delete(chatId)
|
||||
@@ -157,8 +156,7 @@ export class WsBridge {
|
||||
}
|
||||
for (const [, session] of this.sessions) {
|
||||
if (session.reconnectTimer) clearTimeout(session.reconnectTimer)
|
||||
session.ws.removeAllListeners()
|
||||
session.ws.close(1000, 'bridge destroyed')
|
||||
this.closeSocket(session.ws, 1000, 'bridge destroyed')
|
||||
}
|
||||
this.sessions.clear()
|
||||
this.handlers.clear()
|
||||
@@ -178,7 +176,7 @@ export class WsBridge {
|
||||
const prev = this.sessions.get(chatId)
|
||||
if (prev) {
|
||||
if (prev.reconnectTimer) clearTimeout(prev.reconnectTimer)
|
||||
prev.ws.removeAllListeners()
|
||||
this.closeSocket(prev.ws, 1000, 'session replaced')
|
||||
}
|
||||
|
||||
const session: Session = {
|
||||
@@ -240,6 +238,21 @@ export class WsBridge {
|
||||
})
|
||||
}
|
||||
|
||||
private closeSocket(ws: WebSocket, code: number, reason: string): void {
|
||||
ws.removeAllListeners()
|
||||
if (ws.readyState === WebSocket.CLOSED) return
|
||||
|
||||
// `ws.close()` aborts an in-flight handshake by emitting an asynchronous
|
||||
// error before close. Keep a temporary sink after detaching the session
|
||||
// listeners so teardown cannot surface an unhandled EventEmitter error.
|
||||
const swallowTeardownError = () => {}
|
||||
ws.on('error', swallowTeardownError)
|
||||
ws.once('close', () => {
|
||||
ws.removeListener('error', swallowTeardownError)
|
||||
})
|
||||
ws.close(code, reason)
|
||||
}
|
||||
|
||||
/** Wait until the WebSocket for chatId is open. Resolves false on timeout or error. */
|
||||
waitForOpen(chatId: string, timeoutMs = 10_000): Promise<boolean> {
|
||||
const session = this.sessions.get(chatId)
|
||||
|
||||
Reference in New Issue
Block a user