import { TCPSocketListener } from "bun"; import { describe, expect, test } from "bun:test"; import { WebSocket } from "ws"; const hostname = process.env.HOST || "127.0.0.1"; const port = parseInt(process.env.PORT || "0"); describe("WebSocket", () => { test("short read on payload length", async () => { let server: TCPSocketListener | undefined; let client: WebSocket | undefined; let init = false; try { server = Bun.listen({ socket: { data(socket, data) { if (init) { return; } init = true; const frame = data.toString("utf-8"); if (!frame.startsWith("GET")) { throw new Error("Invalid handshake"); } const magic = /Sec-WebSocket-Key: (.*)\r\n/.exec(frame); if (!magic) { throw new Error("Missing Sec-WebSocket-Key"); } const hasher = new Bun.CryptoHasher("sha1"); hasher.update(magic[1]); hasher.update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); const accept = hasher.digest("base64"); // Respond with a websocket handshake. socket.write( "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + `Sec-WebSocket-Accept: ${accept}\r\n` + "\r\n", ); socket.flush(); // Partially write a websocket text frame with an incomplete big-endian u16 length. socket.write(Uint8Array.from([129, 126, 0])); socket.flush(); // Write the remainder of the websocket text frame. setTimeout(() => { socket.write( Uint8Array.from([ 253, 123, 34, 106, 115, 111, 110, 114, 112, 99, 34, 58, 34, 50, 46, 48, 34, 44, 34, 109, 101, 116, 104, 111, 100, 34, 58, 34, 116, 114, 97, 110, 115, 97, 99, 116, 105, 111, 110, 78, 111, 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 34, 44, 34, 112, 97, 114, 97, 109, 115, 34, 58, 123, 34, 114, 101, 115, 117, 108, 116, 34, 58, 123, 34, 99, 111, 110, 116, 101, 120, 116, 34, 58, 123, 34, 115, 108, 111, 116, 34, 58, 50, 52, 57, 54, 48, 50, 49, 55, 57, 125, 44, 34, 118, 97, 108, 117, 101, 34, 58, 123, 34, 115, 105, 103, 110, 97, 116, 117, 114, 101, 34, 58, 34, 50, 80, 50, 120, 102, 51, 109, 85, 49, 118, 114, 110, 89, 99, 100, 49, 76, 105, 99, 104, 56, 69, 76, 104, 104, 88, 120, 55, 50, 111, 67, 105, 110, 77, 97, 81, 88, 101, 113, 106, 118, 68, 55, 111, 52, 101, 75, 77, 53, 70, 66, 51, 78, 76, 97, 104, 86, 55, 68, 87, 101, 81, 106, 105, 102, 98, 107, 53, 56, 75, 121, 104, 66, 119, 98, 119, 88, 49, 104, 103, 119, 103, 112, 112, 102, 118, 77, 71, 34, 44, 34, 115, 108, 111, 116, 34, 58, 50, 52, 57, 54, 48, 50, 49, 55, 57, 125, 125, 44, 34, 115, 117, 98, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 52, 48, 50, 56, 125, 125, ]), ); socket.flush(); }, 0); }, }, hostname, port, }); const { promise, resolve } = Promise.withResolvers(); client = new WebSocket(`ws://${server.hostname}:${server.port}`); client.addEventListener("error", err => { throw new Error(err.message); }); client.addEventListener("close", err => { if (!err.wasClean) { throw new Error(err.reason); } }); client.addEventListener("message", event => resolve(event.data.toString("utf-8"))); expect(await promise).toEqual( `{"jsonrpc":"2.0","method":"transactionNotification","params":{"result":{"context":{"slot":249602179},"value":{"signature":"2P2xf3mU1vrnYcd1Lich8ELhhXx72oCinMaQXeqjvD7o4eKM5FB3NLahV7DWeQjifbk58KyhBwbwX1hgwgppfvMG","slot":249602179}},"subscription":4028}}`, ); } finally { client?.close(); server?.stop(true); } }); });