ServerWebSocket: send Blob bytes instead of "[object Blob]"

Merged
RO
3mo ago
+255-35

What

ServerWebSocket.send(blob), ws.publish(topic, blob), ws.sendBinary(blob), ws.publishBinary(topic, blob), ws.ping(blob), ws.pong(blob), and server.publish(topic, blob) now send the Blob's bytes as a binary frame, matching the client-side WebSocket.send(blob) and npm ws.

Previously:

  • send / publish fell through to ToString and shipped the literal text "[object Blob]" (return code 13), silently corrupting data.
  • sendBinary / publishBinary / ping / pong rejected Blobs with a TypeError.

So passing the same Blob to the client WebSocket.send, the server ws.send, and ws.sendBinary produced three different results.

Repro

const server = Bun.serve({
  port: 0,
  fetch: (req, s) => (s.upgrade(req) ? undefined : new Response("no")),
  websocket: { open(ws) { globalThis.sws = ws; ws.subscribe("t"); }, message() {} },
});
const c = new WebSocket(`ws://127.0.0.1:${server.port}/`);
c.binaryType = "arraybuffer";
c.onmessage = e =>
  console.log("client got:", typeof e.data === "string" ? JSON.stringify(e.data) : new Uint8Array(e.data));
c.onopen = () => {
  const blob = new Blob([new Uint8Array([1, 2, 3, 4])]);
  console.log("ws.send(blob) rc =", sws.send(blob));           // before: 13, after: 4
  console.log("server.publish rc =", server.publish("t", blob)); // before: 13, after: 4
};
// before: client got: "[object Blob]"  "[object Blob]"
// after:  client got: Uint8Array [1,2,3,4]  Uint8Array [1,2,3,4]

Cause

send() / publish() check message_value.as_array_buffer() and on miss fall straight through to message_value.to_js_string(), which is the JS ToString coercion. Blobs (and every other object) become "[object Blob]".

Fix

Insert a value.as_class_ref::<Blob>() check between the ArrayBuffer check and the string coercion at all seven sites, sending blob.shared_view() as Opcode::Binary. A shared blob_payload() helper does the detection and rejects file/S3-backed blobs (Bun.file(), Bun.s3()) with a TypeError that points at await blob.bytes(), since these send paths are synchronous and return a byte count so async I/O cannot happen there.

sendBinary / publishBinary error messages now say "Blob or BufferSource" instead of "an ArrayBufferView".

Type definitions and the websockets doc page are updated to include Blob in the unions.

Notes

Non-Blob objects passed to send / publish still coerce via ToString (e.g. ws.send(42) sends "42"). Tightening that to a TypeError the way npm ws does would be a separate behavior change.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/websocket/websocket-server.test.ts