perspective-dev/perspective · warning · Error

WebSocket message dropped

Error message

WebSocket message dropped (${ws.readyState})

What it means

The perspective WebSocket client throws this when `send_message` is called while the socket is still CONNECTING (readyState 0) — the connection has not finished opening, so the outgoing message is dropped rather than sent. Unlike the transport error, `handle_error`/reconnect is not invoked; the message is simply discarded and the error thrown to the caller.

Solutions

  1. Await the client's connection promise (or the WebSocket `open` event) before calling send_message or any API that sends.
  2. Gate sends on readyState: wait/poll until `ws.readyState === WebSocket.OPEN`.
  3. Serialize sends behind the connection lifecycle: queue messages raised while CONNECTING and flush them on `open`.
  4. In reconnect logic, ensure in-flight/pending requests are re-attached to the newly opened socket instead of the old one.

Example fix

// before
const client = websocket("ws://host/perspective");
client.send_message(openTableReq); // socket still CONNECTING -> dropped

// after
const client = await websocket("ws://host/perspective"); // await connection
// or: await new Promise(r => client.ws.addEventListener("open", r));
client.send_message(openTableReq);
Defensive patterns

Strategy: retry

Validate before calling

async function waitForOpen(ws, timeoutMs = 10000) {
  if (ws.readyState === WebSocket.OPEN) return;
  await new Promise((resolve, reject) => {
    const t = setTimeout(() => reject(new Error("connect timeout")), timeoutMs);
    ws.addEventListener("open", () => { clearTimeout(t); resolve(); }, { once: true });
    ws.addEventListener("error", () => { clearTimeout(t); reject(new Error("connect failed")); }, { once: true });
  });
}

Type guard

function isOpen(ws: WebSocket): boolean {
  return ws.readyState === WebSocket.OPEN;
}

Try / catch

try {
  client.send_message(msg);
} catch (e) {
  if (/^WebSocket message dropped \(0\)/.test(e.message)) {
    await waitForOpen(client.ws);
    client.send_message(msg); // retry once open
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `send_message` (directly or via a higher-level API) between initiating the WebSocket connection and receiving its `open` event — e.g. sending immediately after constructing the client without awaiting connection, or racing requests against a reconnect in progress.

Common situations: Not awaiting the promise/`open` event from `websocket()` before issuing table/view commands; firing requests in a framework mount lifecycle before the async connection resolves; reconnect logic that recreates the socket while pending calls still target the new CONNECTING socket.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of perspective-dev/perspective@11c8238c0c (2026-09-09). Data as JSON: /api/errors/ce9c6093de5b3893. Report an issue: GitHub.

Appendix: source

Thrown at rust/perspective-js/src/ts/websocket.ts:80

        ws.onmessage = (event) => {
            client.handle_response(event.data);
        };

        await receiver;
    }

    async function send_message(proto: Uint8Array) {
        if (
            ws.readyState === WebSocket.CLOSING ||
            ws.readyState === WebSocket.CLOSED
        ) {
            const msg = `WebSocket transport error (${ws.readyState})`;
            client.handle_error(msg, connect);
            throw new Error(msg);
        } else if (ws.readyState === WebSocket.CONNECTING) {
            const msg = `WebSocket message dropped (${ws.readyState})`;
            throw new Error(msg);
        } else {
            const buffer = proto.slice().buffer;
            ws.send(buffer);
        }
    }

    async function on_close() {
        console.debug("Closing WebSocket");
        ws.close();
    }

    client = new Client(send_message, on_close);
    await connect();
    return client;
}

View on GitHub (pinned to 11c8238c0c)