socketio / socketio/bun-engine
A Uint8Array payload is sent as a text frame
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 75
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Disclosure: this is an AI finding, made while implementing CRDT document sync
over Socket.IO. The diagnosis and the suggested patch below were produced and
verified by an AI agent; a human reviewed them before filing.
@socket.io/bun-engine@0.1.1 decides whether a payload is binary using
Buffer.isBuffer alone. Any binary payload that is not a Node Buffer — a
plain Uint8Array, a DataView, an ArrayBuffer — falls through to the text
arm and is emitted as PACKET_TYPES.get(type) + String(data).
dist/parser.js:
encodePacket({ type, data }, supportsBinary) {
if (Buffer.isBuffer(data)) {
return supportsBinary ? data : "b" + data.toString("base64");
} else {
return PACKET_TYPES.get(type) + (data || ""); // <- binary lands here
}
}
Buffer.isBuffer(new Uint8Array()) is false, so a Socket.IO packet carrying a
Uint8Array attachment goes out as "4" + "1,2,3,...". The client is mid
binary reconstruction and throws:
Error: got plaintext data when reconstructing a packet
at add (socket.io-parser/build/esm-debug/index.js:132)
The ack never arrives and the emit is lost, with no error on the server side.
That last part is what makes it expensive to find: the sender looks healthy.
Reproduction
Any server-side socket.emit('x', new Uint8Array([1, 2, 3]), ack) while running
on bun-engine. It shows up immediately with a CRDT library — json-crdt's
Model.toBinary() and Patch.toBinary() both return a plain Uint8Array, so
every document and every patch is affected.
Why Node's engine.io is unaffected
engine.io-parser checks data instanceof ArrayBuffer || isView(data), which
covers every typed array. Code that works on the Node adapter therefore breaks
silently when moved to this one.
Suggested fix
Mirror engine.io-parser's check, and normalise before the base64 arm —
toString('base64') on a bare Uint8Array yields "1,2,3", so that arm needs
a real Buffer too:
const binary = Buffer.isBuffer(data)
? data
: data instanceof ArrayBuffer
? Buffer.from(data)
: ArrayBuffer.isView(data)
? Buffer.from(data.buffer, data.byteOffset, data.byteLength)
: null;
if (binary) {
return supportsBinary ? binary : "b" + binary.toString("base64");
}
Buffer.from(view.buffer, view.byteOffset, view.byteLength) is zero-copy and
correct for a view over a larger buffer, which a bare Buffer.from(view) is
not.
Verified against bun 1.4.0, socket.io@4.8.3, engine.io-client@6.6.6, with
the patch applied and removed.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at encodePacket in dist/parser.js and compare its binary check with the engine.io-parser behavior described in the issue. Reproduce socket.emit with a Uint8Array, DataView, and ArrayBuffer on bun-engine, including the non-binary transport path. Done means typed-array payloads remain binary and base64 encoding works correctly without the client reporting plaintext during reconstruction.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100