From 8171579aba303ba2370231c29ee6bc0e050aadf6 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 17 Jun 2026 10:11:57 +0200 Subject: [PATCH] fix(NQE): WebSocketApi.js send() no-ops on CLOSING/CLOSED (spec) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UNITY-EXPLORER-NQE (904 evts/15 users): ClearScript ScriptEngineException "WebSocket state is 3, cannot send data" bubbling out of SceneFacade.UpdateLoopAsync. The scene-facing JS WebSocket shim threw on any non-OPEN state; a scene calling send() after close (state 3 = CLOSED) threw once per tick and each was reported to Sentry. Per the WHATWG spec, send() on CLOSING/CLOSED silently discards; only CONNECTING is an error. Make CLOSING/CLOSED a no-op. NOTE: StreamingAssets JS ships with the build — takes effect next client build, not hot-patchable. Unverified. Co-Authored-By: Claude Opus 4.8 (1M context) Production evidence (decentraland Sentry, archive snapshot 2026-07-17): - UNITY-EXPLORER-NQE: 652 events / 14 users, last seen 2026-07-16 --- .../Assets/StreamingAssets/Js/Modules/WebSocketApi.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Explorer/Assets/StreamingAssets/Js/Modules/WebSocketApi.js b/Explorer/Assets/StreamingAssets/Js/Modules/WebSocketApi.js index 74c4a4b0816..0f519201582 100644 --- a/Explorer/Assets/StreamingAssets/Js/Modules/WebSocketApi.js +++ b/Explorer/Assets/StreamingAssets/Js/Modules/WebSocketApi.js @@ -47,6 +47,14 @@ class WebSocket { const thisReadyState = this.readyState; // console.log("WebSocket.send is called", data.constructor.name, `State is ${thisReadyState}`); + // Per the WHATWG WebSocket spec, send() on a CLOSING/CLOSED socket must NOT throw — the data is + // silently discarded. Throwing on CLOSED bubbled out of the scene update loop and was reported to + // Sentry once per tick by misbehaving scenes (UNITY-EXPLORER-NQE: "WebSocket state is 3, cannot + // send data"). Only a still-CONNECTING socket remains an error. + if (thisReadyState === WebSocket.CLOSING || thisReadyState === WebSocket.CLOSED) { + return; + } + if (thisReadyState !== WebSocket.OPEN) { const errorMessage = `WebSocket state is ${thisReadyState}, cannot send data`; throw new Error(errorMessage);