{"record":{"id":"64ae4fd551de8346","repo":"perspective-dev/perspective","slug":"websocket-transport-error-ws-readystate","errorCode":null,"errorMessage":"WebSocket transport error (${ws.readyState})","messagePattern":"WebSocket transport error \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"rust/perspective-js/src/ts/websocket.ts","lineNumber":77,"sourceCode":"            client.handle_error(msg, connect);\n            reject(msg);\n        };\n\n        ws.onmessage = (event) => {\n            client.handle_response(event.data);\n        };\n\n        await receiver;\n    }\n\n    async function send_message(proto: Uint8Array) {\n        if (\n            ws.readyState === WebSocket.CLOSING ||\n            ws.readyState === WebSocket.CLOSED\n        ) {\n            const msg = `WebSocket transport error (${ws.readyState})`;\n            client.handle_error(msg, connect);\n            throw new Error(msg);\n        } else if (ws.readyState === WebSocket.CONNECTING) {\n            const msg = `WebSocket message dropped (${ws.readyState})`;\n            throw new Error(msg);\n        } else {\n            const buffer = proto.slice().buffer;\n            ws.send(buffer);\n        }\n    }\n\n    async function on_close() {\n        console.debug(\"Closing WebSocket\");\n        ws.close();\n    }\n\n    client = new Client(send_message, on_close);\n    await connect();\n    return client;\n}","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/perspective-dev/perspective/blob/11c8238c0c9c0b9e490b5a6a2dc277afad1e980a/rust/perspective-js/src/ts/websocket.ts#L59-L95","documentation":"The perspective WebSocket client throws this when `send_message` is called while the underlying WebSocket is in the CLOSING (2) or CLOSED (3) readyState — the message cannot be transported. The error is first reported to the client via `client.handle_error` (which may trigger a reconnect) and then thrown to the caller.","triggerScenarios":"Calling `client.send_message(...)` (or any higher-level API that sends, e.g. table/view requests) after the WebSocket has closed — server shutdown, network drop, explicit `close()`, or idle/keepalive failure — before the reconnect completes.","commonSituations":"Server restart or proxy timeout closing the socket while the app continues issuing queries; calling send during a reconnect window; forgetting to await an initial connection promise; firing requests after `client.close()` was called.","solutions":["Check `client.is_connected()` / the WebSocket readyState before sending, and skip or queue messages when not open.","Await connection before sending: wrap sends in the client's connect/open promise (e.g. `await client.connect()` or the socket's `open` event).","Add a retry wrapper that listens for the reconnection attempt triggered by handle_error and resends queued messages once readyState is OPEN.","Verify the server/proxy keepalive configuration (timeouts, ping intervals) if closures happen during idle periods."],"exampleFix":"// before\nconst client = await websocket(\"ws://host/perspective\");\nsetInterval(() => client.send_message(makeRequest()), 1000); // throws after socket closes\n\n// after\nconst client = await websocket(\"ws://host/perspective\");\nasync function safeSend(req) {\n  if (client.ws.readyState !== WebSocket.OPEN) {\n    await reconnectAndWait(client);\n  }\n  try {\n    client.send_message(req);\n  } catch (e) {\n    if (String(e.message).startsWith(\"WebSocket transport error\")) {\n      await reconnectAndWait(client);\n      client.send_message(req);\n    } else throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":"function canSend(client) {\n  return client.ws && client.ws.readyState === WebSocket.OPEN;\n}\nif (!canSend(client)) await reconnectAndWait(client);","typeGuard":"function isSocketOpen(ws: WebSocket | undefined): ws is WebSocket & { readyState: typeof WebSocket.OPEN } {\n  return !!ws && ws.readyState === WebSocket.OPEN;\n}","tryCatchPattern":"try {\n  client.send_message(msg);\n} catch (e) {\n  if (/^WebSocket transport error \\((2|3)\\)/.test(e.message)) {\n    await reconnect(client);\n    client.send_message(msg); // resend after reconnect\n  } else throw e;\n}","preventionTips":["Always await the connection/open promise before sending.","Subscribe to the socket's `close` event to pause senders and resume after reconnect.","Implement a message queue that buffers sends while the socket is not OPEN.","Configure server/proxy keepalive pings to avoid idle closes mid-session."],"tags":["websocket","network","connection-state","race-condition"],"backgroundTag":"connection-refused","analyzedSha":"11c8238c0c9c0b9e490b5a6a2dc277afad1e980a","analyzedAt":"2026-09-09T00:35:19.377Z","contentChangedAt":"2026-09-09T00:35:19.377Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}