From a4ab82b98137cefe4efbca18d40aef367ca10686 Mon Sep 17 00:00:00 2001 From: sephirith Date: Mon, 10 Aug 2026 20:42:46 +1000 Subject: [PATCH] TypeScript SDK: route websocket errors on established connections to onDisconnect Fixes #5706. ws.onerror previously emitted connectError and disabled the send path for every websocket error, even mid-session, leaving the client silently stalled with no disconnect to trigger reconnect handling. Errors on an established connection now close the socket so the onclose path emits disconnect, with the error passed through per the documented onDisconnect contract. Pre-connect errors emit connectError as before. --- .../src/sdk/db_connection_impl.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/bindings-typescript/src/sdk/db_connection_impl.ts b/crates/bindings-typescript/src/sdk/db_connection_impl.ts index cf73bc270bb..7f276683aa2 100644 --- a/crates/bindings-typescript/src/sdk/db_connection_impl.ts +++ b/crates/bindings-typescript/src/sdk/db_connection_impl.ts @@ -173,6 +173,21 @@ export class DbConnectionImpl */ isDisconnectRequested = false; + /** + * Whether the initial connection handshake completed, i.e. the + * `InitialConnection` message was received and `onConnect` was invoked. + * Used to route websocket errors on an established connection to the + * `disconnect` path instead of `connectError`. + */ + #everConnected = false; + + /** + * The websocket error that ended an established connection, if any. + * Passed to the `disconnect` emit so `onDisconnect` callbacks receive + * the error that caused the disconnect, per their documented contract. + */ + #connectionError?: ErrorEvent = undefined; + /** * Whether the underlying websocket has entered `CLOSING` (2) or `CLOSED` * (3). This becomes true even when the browser never delivered an @@ -378,10 +393,19 @@ export class DbConnectionImpl this.ws.onclose = () => { this.isActive = false; - this.#emitter.emit('disconnect', this); + this.#emitter.emit('disconnect', this, this.#connectionError); }; this.ws.onerror = (e: ErrorEvent) => { this.isActive = false; + if (this.#everConnected) { + // An error on an established connection is not a connect + // failure. Record it and close the socket so the `onclose` -> + // 'disconnect' path handles teardown, per the documented + // `onDisconnect` contract. + this.#connectionError = e; + this.ws?.close(); + return; + } this.#emitter.emit('connectError', this, e); }; this.ws.onopen = this.#handleOnOpen.bind(this); @@ -909,6 +933,7 @@ export class DbConnectionImpl this.token = serverMessage.value.token; } this.#setConnectionId(serverMessage.value.connectionId); + this.#everConnected = true; this.#emitter.emit('connect', this, this.identity, this.token); break; }