v157 · web bluetooth

Chunk planner

Every ATT write without response costs a 3-byte header and one connection event, whether it carries 20 bytes or 500. This works out what a given payload costs at a given write size — and what the same payload costs if you keep assuming 20.

Plan a transfer

The write size is the payload the link can carry, which is the ATT MTU minus the 3-byte ATT header. That is why maxWriteWithoutResponseSize is 20 when the MTU is the 23-byte default — and why reading it beats assuming it.

Cost of this payload at each size
write sizepacketsheader bytesoverheadvs 20 bytes

Packets needed, to scale

code path

const max = server.maxWriteWithoutResponseSize ?? 20;  // 20 is the floor
for (let offset = 0; offset < payload.byteLength; offset += max) {
  await characteristic.writeValueWithoutResponse(
    payload.subarray(offset, offset + max)
  );
}

The ?? 20 matters: on a browser without the property the expression is undefined, and subarray(offset, offset + undefined) silently produces an empty slice — a loop that writes nothing forever.

see also