Is there an existing issue for this?
SDK Version
6.4.0 (@optimizely/optimizely-sdk); also on master as of June 2026. Node.js 18+ (tested on 24).
Current Behavior
Summary
RequestHandler.makeRequest (Node) sets the content-length header from the request body's JavaScript string .length (UTF-16 code-unit count), but writes the body to the socket as UTF-8. For any request body containing multi-byte UTF-8 characters (non-Latin text, full-width punctuation, emoji, accented characters), the header under-reports the actual byte size, so the receiving server gets a truncated body.
For the event dispatcher this shows up as 400 responses from the events endpoint (https://logx.optimizely.com/v1/events) with an "Invalid json" / parse error, because the JSON is cut off mid-string.
Affected code
lib/utils/http_request_handler/request_handler.node.ts, in makeRequest:
headers: {
...headers,
'content-length': String(data?.length || 0), // ← UTF-16 code units, not bytes
},
...
if (data) {
request.write(data); // ← written as UTF-8
}
Present in @optimizely/optimizely-sdk@6.4.0 and still on master as of June 2026.
Why it's wrong
String.prototype.length counts UTF-16 code units, not bytes; request.write(string) encodes as UTF-8 by default. The two differ for any character outside the single-byte range, so Content-Length is too small and the body is truncated.
Impact
- Any event whose payload contains a multi-byte character is rejected (HTTP 400, truncated/invalid JSON).
- Events are batched, so a single multi-byte event poisons the entire batch's
Content-Length — co-batched ASCII-only events fail too.
- Affects any app sending non-ASCII tag values/attributes (Japanese, emoji, accented names, smart quotes, etc.). It looks intermittent/random because it depends on the byte composition of each batch.
Expected Behavior
Content-Length should equal the UTF-8 byte length of the request body, so multi-byte payloads are sent intact and accepted by the events endpoint.
Steps To Reproduce
- @optimizely/optimizely-sdk: 6.4.0 (also reproduces on
master)
- Node.js: 18+ (tested on 24)
- Platform: Node server
Standalone Node script (no SDK required) that mimics the handler — sets Content-Length from String.length and writes the body as UTF-8:
const http = require('http');
const body = '{"searchTerm":"2022 ONE PIECE ACE OP02-013"}'; // full-width spaces (U+3000)
const server = http.createServer((req, res) => {
const chunks = [];
req.on('data', (c) => chunks.push(c));
req.on('end', () => {
const received = Buffer.concat(chunks).toString('utf8');
let parse;
try { JSON.parse(received); parse = 'OK'; }
catch (e) { parse = 'PARSE ERROR: ' + e.message; }
console.log(`content-length=${req.headers['content-length']} bytesRead=${Buffer.byteLength(received)} parse=${parse}`);
res.end('ok');
});
});
function send(contentLength, port) {
return new Promise((resolve) => {
const req = http.request({ port, method: 'POST',
headers: { 'content-type': 'application/json', 'content-length': String(contentLength), connection: 'close' } },
(res) => { res.on('data', () => {}); res.on('end', resolve); });
req.on('error', () => resolve());
req.write(body); // UTF-8
req.end();
});
}
server.listen(0, async () => {
const { port } = server.address();
console.log(`UTF-16 .length=${body.length} UTF-8 byteLength=${Buffer.byteLength(body)}`);
await send(body.length, port); // SDK behavior
await new Promise((r) => setTimeout(r, 50));
await send(Buffer.byteLength(body), port); // correct
server.close();
});
Output:
UTF-16 .length=44 UTF-8 byteLength=52
content-length=44 bytesRead=44 parse=PARSE ERROR: Unterminated string in JSON at position 36
content-length=52 bytesRead=52 parse=OK
SDK Type
Node
Node Version
Node v24
Browsers impacted
No response
Link
No response
Logs
No response
Severity
No response
Workaround/Solution
Use the UTF-8 byte length for Content-Length:
'content-length': String(data ? Buffer.byteLength(data, 'utf8') : 0),
(Browser / React Native request handlers should use the platform-appropriate byte length, e.g. new TextEncoder().encode(data).length.)
Recent Change
No response
Conflicts
No response
Is there an existing issue for this?
SDK Version
6.4.0 (@optimizely/optimizely-sdk); also on master as of June 2026. Node.js 18+ (tested on 24).
Current Behavior
Summary
RequestHandler.makeRequest(Node) sets thecontent-lengthheader from the request body's JavaScript string.length(UTF-16 code-unit count), but writes the body to the socket as UTF-8. For any request body containing multi-byte UTF-8 characters (non-Latin text, full-width punctuation, emoji, accented characters), the header under-reports the actual byte size, so the receiving server gets a truncated body.For the event dispatcher this shows up as
400responses from the events endpoint (https://logx.optimizely.com/v1/events) with an "Invalid json" / parse error, because the JSON is cut off mid-string.Affected code
lib/utils/http_request_handler/request_handler.node.ts, inmakeRequest:Present in
@optimizely/optimizely-sdk@6.4.0and still onmasteras of June 2026.Why it's wrong
String.prototype.lengthcounts UTF-16 code units, not bytes;request.write(string)encodes as UTF-8 by default. The two differ for any character outside the single-byte range, soContent-Lengthis too small and the body is truncated.Impact
Content-Length— co-batched ASCII-only events fail too.Expected Behavior
Content-Length should equal the UTF-8 byte length of the request body, so multi-byte payloads are sent intact and accepted by the events endpoint.
Steps To Reproduce
master)Standalone Node script (no SDK required) that mimics the handler — sets
Content-LengthfromString.lengthand writes the body as UTF-8:Output:
SDK Type
Node
Node Version
Node v24
Browsers impacted
No response
Link
No response
Logs
No response
Severity
No response
Workaround/Solution
Use the UTF-8 byte length for
Content-Length:(Browser / React Native request handlers should use the platform-appropriate byte length, e.g.
new TextEncoder().encode(data).length.)Recent Change
No response
Conflicts
No response