mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 03:43:11 +08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -2,6 +2,18 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test'
|
||||
import { WsBridge } from '../ws-bridge.js'
|
||||
import { WebSocketServer, type WebSocket as WsServerSocket } from 'ws'
|
||||
|
||||
async function waitFor(
|
||||
predicate: () => boolean,
|
||||
timeoutMs = 500,
|
||||
): Promise<boolean> {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
while (Date.now() < deadline) {
|
||||
if (predicate()) return true
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
}
|
||||
return predicate()
|
||||
}
|
||||
|
||||
describe('WsBridge', () => {
|
||||
let bridge: WsBridge
|
||||
|
||||
@@ -58,8 +70,16 @@ describe('WsBridge', () => {
|
||||
expect(bridge.hasSession('a')).toBe(false)
|
||||
expect(bridge.hasSession('b')).toBe(false)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20))
|
||||
for (const ws of sockets) {
|
||||
const settled = await waitFor(() => (
|
||||
ws.readyState === ws.CLOSED
|
||||
&& ws.listenerCount('open') === 0
|
||||
&& ws.listenerCount('error') === 0
|
||||
&& ws.listenerCount('close') === 0
|
||||
))
|
||||
expect(settled).toBe(true)
|
||||
expect(ws.readyState).toBe(ws.CLOSED)
|
||||
expect(ws.listenerCount('open')).toBe(0)
|
||||
expect(ws.listenerCount('error')).toBe(0)
|
||||
expect(ws.listenerCount('close')).toBe(0)
|
||||
}
|
||||
|
||||
@@ -242,9 +242,31 @@ export class WsBridge {
|
||||
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.
|
||||
if (ws.readyState === WebSocket.CONNECTING) {
|
||||
// Bun's `ws` compatibility layer can remain stuck in CLOSING when a
|
||||
// handshake is aborted. Let the handshake settle, consuming its natural
|
||||
// error, and close normally if the connection opens first.
|
||||
const cleanup = () => {
|
||||
ws.removeListener('open', onOpen)
|
||||
ws.removeListener('error', onError)
|
||||
ws.removeListener('close', onClose)
|
||||
}
|
||||
const onOpen = () => {
|
||||
ws.removeListener('open', onOpen)
|
||||
ws.close(code, reason)
|
||||
}
|
||||
const onError = () => cleanup()
|
||||
const onClose = () => cleanup()
|
||||
|
||||
ws.once('open', onOpen)
|
||||
ws.once('error', onError)
|
||||
ws.once('close', onClose)
|
||||
return
|
||||
}
|
||||
|
||||
// Keep a temporary error sink after detaching the session listeners so a
|
||||
// close-time transport error cannot surface as an unhandled EventEmitter
|
||||
// error.
|
||||
const swallowTeardownError = () => {}
|
||||
ws.on('error', swallowTeardownError)
|
||||
ws.once('close', () => {
|
||||
|
||||
Reference in New Issue
Block a user