From b77d9b887a1d8c2b136b0f91ab47a737309f7e3c Mon Sep 17 00:00:00 2001 From: Rohan Ranjan Prasad Date: Sun, 30 Aug 2026 17:07:31 +0530 Subject: [PATCH] fix(typescript): sequence async websocket decompression to prevent out-of-order frame delivery (fixes #5730) --- .../src/sdk/websocket_decompress_adapter.ts | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/bindings-typescript/src/sdk/websocket_decompress_adapter.ts b/crates/bindings-typescript/src/sdk/websocket_decompress_adapter.ts index 7a8d680afe8..33ecda4c357 100644 --- a/crates/bindings-typescript/src/sdk/websocket_decompress_adapter.ts +++ b/crates/bindings-typescript/src/sdk/websocket_decompress_adapter.ts @@ -15,25 +15,28 @@ export class WebsocketDecompressAdapter implements WebSocketAdapter { this.#ws.onopen = handler; } set onmessage(handler: (msg: { data: Uint8Array }) => void) { - this.#ws.onmessage = async (msg: MessageEvent) => { - let data: Uint8Array; - try { - data = await this.#decompress(new Uint8Array(msg.data)); - } catch (e) { - // A decompression failure (e.g. WebKit's DecompressionStream rejecting - // with "Incomplete compressed input.") would otherwise become an - // unhandled rejection: the frame is silently dropped and the - // connection stalls without ever reaching onDisconnect. Close the - // socket instead so the caller's disconnect/reconnect handling - // takes over. - console.error( - '[SpacetimeDB] WebSocket decompress failed, closing socket:', - e - ); - this.#ws.close(); - return; - } - handler({ data }); + let tail: Promise = Promise.resolve(); + this.#ws.onmessage = (msg: MessageEvent) => { + const pending = this.#decompress(new Uint8Array(msg.data)); + // Mark the rejection handled now: the chain may not reach this frame for + // several ticks, and without this an inflate failure surfaces as an + // unhandledrejection in the meantime. The real error still reaches .catch. + pending.catch(() => {}); + tail = tail + .then(async () => handler({ data: await pending })) + .catch((e) => { + // A decompression failure (e.g. WebKit's DecompressionStream rejecting + // with "Incomplete compressed input.") would otherwise become an + // unhandled rejection: the frame is silently dropped and the + // connection stalls without ever reaching onDisconnect. Close the + // socket instead so the caller's disconnect/reconnect handling + // takes over. + console.error( + '[SpacetimeDB] WebSocket decompress failed, closing socket:', + e + ); + this.#ws.close(); + }); }; } set onerror(handler: (msg: ErrorEvent) => void) {